From 31408540294fef8c51e837f4536bf8d50f13e6f1 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Thu, 5 Jun 2025 16:24:18 -0400 Subject: [PATCH 01/37] added spatial-nback-task timeline version 1.0.0 --- package-lock.json | 608 +- packages/spatial-nback-task/README.md | 36 + .../docs/spatial-nback-task.md | 51 + .../spatial-nback-task/examples/index.html | 59 + packages/spatial-nback-task/jest.config.js | 1 + packages/spatial-nback-task/package-lock.json | 10419 ++++++++++++++++ packages/spatial-nback-task/package.json | 39 + packages/spatial-nback-task/src/index copy.ts | 108 + packages/spatial-nback-task/src/index.ts | 276 + packages/spatial-nback-task/tsconfig.json | 10 + 10 files changed, 11600 insertions(+), 7 deletions(-) create mode 100644 packages/spatial-nback-task/README.md create mode 100644 packages/spatial-nback-task/docs/spatial-nback-task.md create mode 100644 packages/spatial-nback-task/examples/index.html create mode 100644 packages/spatial-nback-task/jest.config.js create mode 100644 packages/spatial-nback-task/package-lock.json create mode 100644 packages/spatial-nback-task/package.json create mode 100644 packages/spatial-nback-task/src/index copy.ts create mode 100644 packages/spatial-nback-task/src/index.ts create mode 100644 packages/spatial-nback-task/tsconfig.json diff --git a/package-lock.json b/package-lock.json index 940e6ed0..a7b8d300 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,9 +1,10 @@ { - "name": "jspsych-timelines", + "name": "@jspsych/jspsych-timelines", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "@jspsych/jspsych-timelines", "workspaces": [ "packages/*", "templates/cli" @@ -2573,9 +2574,10 @@ "dev": true }, "node_modules/@jspsych/plugin-html-keyboard-response": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.0.0.tgz", - "integrity": "sha512-O4tfd91ve4RJr0ilHmnk6rdxWX7IwGnOKbFpi85p1t0POUNm21Kfq39pUnyoSZTWr2anibIS3OuHP2IvUtIoMA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", + "integrity": "sha512-NmtYU9798mEJ6CLQREp6bNHo1e+Ca5zwAznBlREmpUMdOrqBhTLrF5T/Gg4t2MJHm4lFN/fwHJtt0g5vMAoYFg==", + "license": "MIT", "peerDependencies": { "jspsych": ">=7.1.0" } @@ -4044,6 +4046,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bundle-require": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-4.2.1.tgz", + "integrity": "sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.17" + } + }, "node_modules/cac": { "version": "6.7.14", "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", @@ -8063,9 +8081,10 @@ } }, "node_modules/jspsych": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/jspsych/-/jspsych-8.0.2.tgz", - "integrity": "sha512-R7Ve/Gl28PCEGLN4u5y/FA9fgVquJuyrPMxsVMyUfVtGZK2LB/XoyDbln9pHvAkJFmNcOS34kKnq08QbSiOk3w==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/jspsych/-/jspsych-8.2.1.tgz", + "integrity": "sha512-VpAxmkzj72/Y2eTKXt5ksZGFL7VqvgUL+66pUqm7KPUSZsIOSM1rKCtMN42u3fT2N8EonR7LGFFcsAvnZAOykQ==", + "license": "MIT", "dependencies": { "auto-bind": "^4.0.0", "random-words": "^1.1.1", @@ -9173,6 +9192,46 @@ "semver-compare": "^1.0.0" } }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/prettier": { "version": "2.8.8", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", @@ -9948,6 +10007,10 @@ "node": ">= 10.13.0" } }, + "node_modules/spatial-nback-task": { + "resolved": "packages/spatial-nback-task", + "link": true + }, "node_modules/spawndamnit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-2.0.0.tgz", @@ -11611,6 +11674,537 @@ "jspsych": "^8.0.1" } }, + "packages/spatial-nback-task": { + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@jspsych/plugin-html-keyboard-response": "^2.1.0", + "jsPsych": "npm:jspsych@^8.2.1" + }, + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/spatial-nback-task/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "packages/spatial-nback-task/node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "packages/spatial-nback-task/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "packages/spatial-nback-task/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "packages/spatial-nback-task/node_modules/tsup": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", + "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^4.0.0", + "cac": "^6.7.12", + "chokidar": "^3.5.1", + "debug": "^4.3.1", + "esbuild": "^0.17.6", + "execa": "^5.0.0", + "globby": "^11.0.3", + "joycon": "^3.0.1", + "postcss-load-config": "^3.0.1", + "resolve-from": "^5.0.0", + "rollup": "^3.2.5", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.20.3", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "packages/spatial-nback-task/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "packages/spatial-nback-task/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "templates/cli": { "name": "@jspsych-timelines/cli", "version": "0.2.0", diff --git a/packages/spatial-nback-task/README.md b/packages/spatial-nback-task/README.md new file mode 100644 index 00000000..64374a5c --- /dev/null +++ b/packages/spatial-nback-task/README.md @@ -0,0 +1,36 @@ +# spatial-nback-task + +## Overview + +Deployable spatial nback timeline + +## Loading + +*Enter instructions for loading the timeline package here.* + +## Compatibility + +`spatial-nback-task` requires jsPsych v8.0.0 or later. + +## Documentation + +### createTimeline + +#### jsPsychTimelineSpatialNbackTask.createTimeline(jsPsych, { *options* }) ⇒ timeline +Description of the timeline that this plugin generates + +The following parameters can be specified in the **options** parameter. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| parameter | type | default | description | + + +### timelineUnits + + +### utils + +## Author / Citation + +Hunter Farhat \ No newline at end of file diff --git a/packages/spatial-nback-task/docs/spatial-nback-task.md b/packages/spatial-nback-task/docs/spatial-nback-task.md new file mode 100644 index 00000000..9b7fc615 --- /dev/null +++ b/packages/spatial-nback-task/docs/spatial-nback-task.md @@ -0,0 +1,51 @@ +# spatial-nback-task + +Deployable spatial nback timeline + +## Parameters + +### Initialization Parameters + +Initialization parameters can be set when calling `initJsPsych()` + +```js +initJsPsych({ + timelines: [ + {type: jsPsychTimelineSpatialNbackTask, params: {...}} + ] +}) +``` + +Parameter | Type | Default Value | Description +----------|------|---------------|------------ + | | | + +### Trial Parameters + +Trial parameters can be set when adding the timeline to a trial object. + +```js +var trial = { + type: jsPsych..., + timelines: [ + {type: jsPsychTimelineSpatialNbackTask, params: {...}} + ] +} +``` + +Parameter | Type | Default Value | Description +----------|------|---------------|------------ + | | | + +## Data Generated + +Name | Type | Value +-----|------|------ + | | + +## Functions + +If the timeline adds any static functions, list them here. + +### function() + diff --git a/packages/spatial-nback-task/examples/index.html b/packages/spatial-nback-task/examples/index.html new file mode 100644 index 00000000..f9f951e7 --- /dev/null +++ b/packages/spatial-nback-task/examples/index.html @@ -0,0 +1,59 @@ + + + + + Spatial N-Back Task + + + + + + + + + + + \ No newline at end of file diff --git a/packages/spatial-nback-task/jest.config.js b/packages/spatial-nback-task/jest.config.js new file mode 100644 index 00000000..bd1aacef --- /dev/null +++ b/packages/spatial-nback-task/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/spatial-nback-task/package-lock.json b/packages/spatial-nback-task/package-lock.json new file mode 100644 index 00000000..9633be83 --- /dev/null +++ b/packages/spatial-nback-task/package-lock.json @@ -0,0 +1,10419 @@ +{ + "name": "spatial-nback-task", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "spatial-nback-task", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "@jspsych/plugin-html-keyboard-response": "^2.1.0", + "jsPsych": "npm:jspsych@^8.2.1" + }, + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", + "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", + "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.4", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.27.4", + "@babel/types": "^7.27.3", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", + "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.27.5", + "@babel/types": "^7.27.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", + "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/traverse": "^7.27.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", + "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", + "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", + "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", + "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-optimise-call-expression": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", + "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", + "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.27.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", + "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", + "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", + "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", + "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", + "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", + "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", + "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-syntax-flow": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", + "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", + "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", + "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", + "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.27.1.tgz", + "integrity": "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-transform-flow-strip-types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", + "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-typescript": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz", + "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@babel/template": { + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.27.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", + "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.27.3", + "@babel/parser": "^7.27.4", + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", + "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@citation-js/core": { + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.18.tgz", + "integrity": "sha512-EjLuZWA5156dIFGdF7OnyPyWFBW43B8Ckje6Sn/W2RFxHDu0oACvW4/6TNgWT80jhEA4bVFm7ahrZe9MJ2B2UQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2", + "fetch-ponyfill": "^7.1.0", + "sync-fetch": "^0.4.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@citation-js/date": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@citation-js/date/-/date-0.5.1.tgz", + "integrity": "sha512-1iDKAZ4ie48PVhovsOXQ+C6o55dWJloXqtznnnKy6CltJBQLIuLLuUqa8zlIvma0ZigjVjgDUhnVaNU1MErtZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@citation-js/name": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@citation-js/name/-/name-0.4.2.tgz", + "integrity": "sha512-brSPsjs2fOVzSnARLKu0qncn6suWjHVQtrqSUrnqyaRH95r/Ad4wPF5EsoWr+Dx8HzkCGb/ogmoAzfCsqlTwTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@citation-js/plugin-bibtex": { + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.18.tgz", + "integrity": "sha512-TdsZSMpgpfcx2NMPu0KiulEoecllwT5EtRUzAJl2pDsdPD1tUqqbyj/NBi0l8fwNy1r7WwAqSFGiqGPjQWpFdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2", + "moo": "^0.5.1" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@citation-js/core": "^0.7.0" + } + }, + "node_modules/@citation-js/plugin-cff": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-cff/-/plugin-cff-0.6.1.tgz", + "integrity": "sha512-tLjTgsfzNOdQWGn5mNc2NAaydHnlRucSERoyAXLN7u0BQBfp7j5zwdxCmxcQD/N7hH3fpDKMG+qDzbqpJuKyNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/plugin-yaml": "^0.6.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@citation-js/plugin-csl": { + "version": "0.7.18", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.18.tgz", + "integrity": "sha512-cJcOdEZurmtIxNj0d4cOERHpVQJB/mN3YPSDNqfI/xTFRN3bWDpFAsaqubPtMO2ZPpoDS+ZGIP1kggbwCfMmlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/date": "^0.5.0", + "citeproc": "^2.4.6" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "@citation-js/core": "^0.7.0" + } + }, + "node_modules/@citation-js/plugin-github": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-github/-/plugin-github-0.6.1.tgz", + "integrity": "sha512-1ZeSgQ5AoYsa8n2acVooUeRk76oA8rLszYNBjzj5z6MPa11BZlQJ9O+Gy4tHjlImvsENLbLPx5f8/V1VHXaCfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@citation-js/plugin-npm": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-npm/-/plugin-npm-0.6.1.tgz", + "integrity": "sha512-rojJA+l/p2KBpDoY+8n0YfNyQO1Aw03fQR5BN+gXD1LNAP1V+8wqvdPsaHnzPsrhrd4ZXDR7ch/Nk0yynPkJ3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@citation-js/plugin-software-formats": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-software-formats/-/plugin-software-formats-0.6.1.tgz", + "integrity": "sha512-BDF9rqi56K0hoTgYTVANCFVRSbWKC9V06Uap7oa8SjqCTgnHJAy8t/F3NxsyYPPG+zmRsLW9VNbcIsJOl0eu/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/plugin-cff": "^0.6.1", + "@citation-js/plugin-github": "^0.6.1", + "@citation-js/plugin-npm": "^0.6.1", + "@citation-js/plugin-yaml": "^0.6.1", + "@citation-js/plugin-zenodo": "^0.6.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@citation-js/plugin-yaml": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-yaml/-/plugin-yaml-0.6.1.tgz", + "integrity": "sha512-XEVVks1cJTqRbjy+nmthfw/puR6NwRB3fyJWi1tX13UYXlkhP/h45nsv4zjgLLGekdcMHQvhad9MAYunOftGKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-yaml": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@citation-js/plugin-zenodo": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@citation-js/plugin-zenodo/-/plugin-zenodo-0.6.1.tgz", + "integrity": "sha512-bUybENHoZqJ6gheUqgkumjI+mu+fA2bg6VoniDmZTb7Qng9iEpi+IWEAR26/vBE0gK0EWrJjczyDW3HCwrhvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/date": "^0.5.0", + "@citation-js/name": "^0.4.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@gulpjs/messages": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", + "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@gulpjs/to-absolute-glob": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", + "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-negated-glob": "^1.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", + "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.2.0.tgz", + "integrity": "sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/editor": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", + "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", + "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", + "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", + "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", + "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", + "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.5.0.tgz", + "integrity": "sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^2.5.0", + "@inquirer/confirm": "^3.2.0", + "@inquirer/editor": "^2.2.0", + "@inquirer/expand": "^2.3.0", + "@inquirer/input": "^2.3.0", + "@inquirer/number": "^1.1.0", + "@inquirer/password": "^2.2.0", + "@inquirer/rawlist": "^2.3.0", + "@inquirer/search": "^1.1.0", + "@inquirer/select": "^2.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", + "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", + "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", + "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.3", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", + "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", + "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@jspsych/config": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@jspsych/config/-/config-3.2.2.tgz", + "integrity": "sha512-nbZe71SxYcdxAL3K/R8kfTSOfek7nbO6D1RrHjVWDbXdYwwUacE0uIKmfjM84UITpRG4JgdH5zIiVLEqd/KOeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@citation-js/core": "^0.7.14", + "@citation-js/plugin-bibtex": "^0.7.14", + "@citation-js/plugin-csl": "^0.7.14", + "@citation-js/plugin-software-formats": "^0.6.1", + "@rollup/plugin-commonjs": "26.0.1", + "@rollup/plugin-node-resolve": "15.2.3", + "@rollup/plugin-replace": "^6.0.1", + "@sucrase/jest-plugin": "3.0.0", + "@types/gulp": "4.0.17", + "@types/jest": "29.5.8", + "@types/node": "^22.10.10", + "alias-hq": "6.2.4", + "app-root-path": "^3.1.0", + "esbuild": "0.23.1", + "glob": "7.2.3", + "gulp": "5.0.0", + "gulp-cli": "3.0.0", + "gulp-file": "0.4.0", + "gulp-rename": "2.0.0", + "gulp-replace": "1.1.4", + "gulp-zip": "6.0.0", + "jest": "29.7.0", + "jest-canvas-mock": "2.5.0", + "jest-environment-jsdom": "29.7.0", + "merge-stream": "2.0.0", + "rollup": "4.21.2", + "rollup-plugin-dts": "6.1.1", + "rollup-plugin-esbuild": "6.1.1", + "rollup-plugin-modify": "^3.0.0", + "rollup-plugin-node-externals": "7.1.3", + "sucrase": "3.34.0", + "tslib": "2.6.2", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@jspsych/plugin-html-keyboard-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", + "integrity": "sha512-NmtYU9798mEJ6CLQREp6bNHo1e+Ca5zwAznBlREmpUMdOrqBhTLrF5T/Gg4t2MJHm4lFN/fwHJtt0g5vMAoYFg==", + "license": "MIT", + "peerDependencies": { + "jspsych": ">=7.1.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "26.0.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.1.tgz", + "integrity": "sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "commondir": "^1.0.1", + "estree-walker": "^2.0.2", + "glob": "^10.4.1", + "is-reference": "1.2.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=16.0.0 || 14 >= 14.17" + }, + "peerDependencies": { + "rollup": "^2.68.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.2.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", + "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-builtin-module": "^3.2.1", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-replace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", + "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", + "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", + "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", + "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", + "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", + "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", + "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", + "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", + "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", + "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", + "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", + "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", + "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", + "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", + "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", + "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", + "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sucrase/jest-plugin": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sucrase/jest-plugin/-/jest-plugin-3.0.0.tgz", + "integrity": "sha512-VRY6YKYImVWiRg1H3Yu24hwB1UPJDSDR62R/n+lOHR3+yDrfHEIAoddJivblMYN6U3vD+ndfTSrecZ9Jl+iGNw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "jest": ">=27", + "sucrase": ">=3.25.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", + "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/expect": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", + "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/glob-stream": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha512-vctgrT9AH/GK3TRaIbRUU0TZn12GBU4kzelZdPyJp1Sc8L/6Wrq21UrtN4+x4saqTg6COUIUtFV6JSYcVln/EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/picomatch": "*", + "@types/streamx": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/gulp": { + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.17.tgz", + "integrity": "sha512-+pKQynu2C/HS16kgmDlAicjtFYP8kaa86eE9P0Ae7GB5W29we/E2TIdbOWtEZD5XkpY+jr8fyqfwO6SWZecLpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/undertaker": ">=1.2.6", + "@types/vinyl-fs": "*", + "chokidar": "^3.3.1" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.8", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", + "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/jsdom": { + "version": "20.0.1", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", + "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^7.0.0" + } + }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "22.15.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", + "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/picomatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-4.0.0.tgz", + "integrity": "sha512-J1Bng+wlyEERWSgJQU1Pi0HObCLVcr994xT/M+1wcl/yNRTGBupsCxthgkdYG+GCOMaQH7iSVUY3LJVBBqG7MQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/streamx": { + "version": "2.9.5", + "resolved": "https://registry.npmjs.org/@types/streamx/-/streamx-2.9.5.tgz", + "integrity": "sha512-IHYsa6jYrck8VEdSwpY141FTTf6D7boPeMq9jy4qazNrFMA4VbRz/sw5LSsfR7jwdDcx0QKWkUexZvsWBC2eIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/undertaker": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.11.tgz", + "integrity": "sha512-j1Z0V2ByRHr8ZK7eOeGq0LGkkdthNFW0uAZGY22iRkNQNL9/vAV0yFPr1QN3FM/peY5bxs9P+1f0PYJTQVa5iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/undertaker-registry": "*", + "async-done": "~1.3.2" + } + }, + "node_modules/@types/undertaker-registry": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.4.tgz", + "integrity": "sha512-tW77pHh2TU4uebWXWeEM5laiw8BuJ7pyJYDh6xenOs75nhny2kVgwYbegJ4BoLMYsIrXaBpKYaPdYO3/udG+hg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/vinyl": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", + "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/expect": "^1.20.4", + "@types/node": "*" + } + }, + "node_modules/@types/vinyl-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-3.0.6.tgz", + "integrity": "sha512-e9GHnmABNUnJ4D99OjVO5s87TfYpmEs7/VKbVS/rt0KkZnKA2vIMyEC5K0H7W/XBiRUO4pdaZxvVUmzjRnrydA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/glob-stream": "*", + "@types/node": "*", + "@types/vinyl": "*" + } + }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "deprecated": "Use your platform's native atob() and btoa() methods instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/acorn": { + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", + "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.1.0", + "acorn-walk": "^8.0.2" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/alias-hq": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/alias-hq/-/alias-hq-6.2.4.tgz", + "integrity": "sha512-6KGuO4XB3PbvTfP+WJEJR2dGMy6h0UyLa2/kZOeeD/UIrYoaUAQwKdLovYyCpgZErYD1d3zIuZh6GPMDADvF4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "colors": "^1.4.0", + "get-tsconfig": "^4.8.0", + "glob": "^7.2.3", + "inquirer": "^10.1.6", + "jscodeshift": "^0.16.1", + "json5": "^2.2.3", + "module-alias": "^2.2.3", + "node-fetch": "^2.7.0", + "open": "^7.4.2", + "vue-jscodeshift-adapter": "^2.2.1" + }, + "bin": { + "alias-hq": "bin/alias-hq" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/app-root-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", + "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-types": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", + "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/async-settle": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", + "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/async-settle/node_modules/async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/auto-bind": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", + "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/b4a": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", + "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/bach": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", + "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "async-settle": "^2.0.0", + "now-and-later": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/bach/node_modules/async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/bare-events": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", + "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", + "dev": true, + "license": "Apache-2.0", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binaryextensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", + "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", + "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001718", + "electron-to-chromium": "^1.5.160", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bundle-require": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-4.2.1.tgz", + "integrity": "sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "load-tsconfig": "^0.2.3" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "peerDependencies": { + "esbuild": ">=0.17" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001721", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", + "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/citeproc": { + "version": "2.4.63", + "resolved": "https://registry.npmjs.org/citeproc/-/citeproc-2.4.63.tgz", + "integrity": "sha512-68F95Bp4UbgZU/DBUGQn0qV3HDZLCdI9+Bb2ByrTaNJDL5VEm9LqaiNaxljsvoaExSLEXe1/r6n2Z06SCzW3/Q==", + "dev": true, + "license": "CPAL-1.0 OR AGPL-1.0" + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", + "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-props": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", + "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "each-props": "^3.0.0", + "is-plain-object": "^5.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssfontparser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", + "integrity": "sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "dev": true, + "license": "MIT" + }, + "node_modules/dedent": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", + "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "deprecated": "Use your platform's native DOMException instead", + "dev": true, + "license": "MIT", + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/each-props": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", + "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/easy-transform-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/easy-transform-stream/-/easy-transform-stream-1.0.1.tgz", + "integrity": "sha512-ktkaa6XR7COAR3oj02CF3IOgz2m1hCaY3SfzvKT4Svt2MhHw9XCt+ncJNWfe2TGz31iqzNGZ8spdKQflj+Rlog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.165", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", + "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", + "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "homedir-polyfill": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "license": "MIT" + }, + "node_modules/external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", + "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fastest-levenshtein": "^1.0.7" + } + }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fetch-ponyfill": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz", + "integrity": "sha512-FhbbL55dj/qdVO3YNK7ZEkshvj3eQ7EuIGV2I6ic/2YiocvyWv+7jg2s4AyS0wdRU75s3tA8ZxI/xPigb0v5Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "node-fetch": "~2.6.1" + } + }, + "node_modules/fetch-ponyfill/node_modules/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/fetch-ponyfill/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fetch-ponyfill/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/fetch-ponyfill/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/find-cache-dir/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/find-cache-dir/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/findup-sync": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", + "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-file": "^1.0.0", + "is-glob": "^4.0.3", + "micromatch": "^4.0.4", + "resolve-dir": "^1.0.1" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/fined": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", + "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "is-plain-object": "^5.0.0", + "object.defaults": "^1.1.0", + "object.pick": "^1.3.0", + "parse-filepath": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/flagged-respawn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", + "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/flow-parser": { + "version": "0.272.2", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.272.2.tgz", + "integrity": "sha512-AMMHyzXP4T6ran6yIqaPniH8BDSdJf3T8PJVfnTnPAdILA1tt8nCSxiJAWRk2ZKiuos3OsrO2NWe8XNIcPw+Qw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", + "dev": true, + "license": "MIT", + "dependencies": { + "for-in": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs-mkdirp-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", + "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.8", + "streamx": "^2.12.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", + "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/glob-stream": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", + "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@gulpjs/to-absolute-glob": "^4.0.0", + "anymatch": "^3.1.3", + "fastq": "^1.13.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "is-negated-glob": "^1.0.0", + "normalize-path": "^3.0.0", + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-stream/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-watcher": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", + "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-done": "^2.0.0", + "chokidar": "^3.5.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/glob-watcher/node_modules/async-done": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", + "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.4", + "once": "^1.4.0", + "stream-exhaust": "^1.0.2" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/global-modules": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", + "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^1.0.1", + "is-windows": "^1.0.1", + "resolve-dir": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", + "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.2", + "homedir-polyfill": "^1.0.1", + "ini": "^1.3.4", + "is-windows": "^1.0.1", + "which": "^1.2.14" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/global-prefix/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glogg": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", + "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", + "dev": true, + "license": "MIT", + "dependencies": { + "sparkles": "^2.1.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gulp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", + "integrity": "sha512-S8Z8066SSileaYw1S2N1I64IUc/myI2bqe2ihOBzO6+nKpvNSg7ZcWJt/AwF8LC/NVN+/QZ560Cb/5OPsyhkhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob-watcher": "^6.0.0", + "gulp-cli": "^3.0.0", + "undertaker": "^2.0.0", + "vinyl-fs": "^4.0.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-cli": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz", + "integrity": "sha512-RtMIitkT8DEMZZygHK2vEuLPqLPAFB4sntSxg4NoDta7ciwGZ18l7JuhCTiS5deOJi2IoK0btE+hs6R4sfj7AA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@gulpjs/messages": "^1.1.0", + "chalk": "^4.1.2", + "copy-props": "^4.0.0", + "gulplog": "^2.2.0", + "interpret": "^3.1.1", + "liftoff": "^5.0.0", + "mute-stdout": "^2.0.0", + "replace-homedir": "^2.0.0", + "semver-greatest-satisfied-range": "^2.0.0", + "string-width": "^4.2.3", + "v8flags": "^4.0.0", + "yargs": "^16.2.0" + }, + "bin": { + "gulp": "bin/gulp.js" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulp-file": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/gulp-file/-/gulp-file-0.4.0.tgz", + "integrity": "sha512-3NPCJpAPpbNoV2aml8T96OK3Aof4pm4PMOIa1jSQbMNSNUUXdZ5QjVgLXLStjv0gg9URcETc7kvYnzXdYXUWug==", + "dev": true, + "license": "BSD", + "dependencies": { + "through2": "^0.4.1", + "vinyl": "^2.1.0" + } + }, + "node_modules/gulp-plugin-extras": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/gulp-plugin-extras/-/gulp-plugin-extras-0.3.0.tgz", + "integrity": "sha512-I/kOBSpo61QsGQZcqozZYEnDseKvpudUafVVWDLYgBFAUJ37kW5R8Sjw9cMYzpGyPUfEYOeoY4p+dkfLqgyJUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/vinyl": "^2.0.9", + "chalk": "^5.3.0", + "easy-transform-stream": "^1.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gulp-plugin-extras/node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/gulp-rename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/gulp-replace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.4.tgz", + "integrity": "sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/vinyl": "^2.0.4", + "istextorbinary": "^3.0.0", + "replacestream": "^4.0.3", + "yargs-parser": ">=5.0.0-security.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gulp-zip": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-6.0.0.tgz", + "integrity": "sha512-fPGvNve2dBoZxGKcviTU7mOa77eQibyhwgGLTxnF+ZCKX8RFaTZKkPbdPnmw0r4TNPRjPCkQB/0VuP+MzgkEYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-stream": "^8.0.1", + "gulp-plugin-extras": "^0.3.0", + "vinyl": "^3.0.0", + "yazl": "^2.5.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + }, + "peerDependencies": { + "gulp": ">=4" + }, + "peerDependenciesMeta": { + "gulp": { + "optional": true + } + } + }, + "node_modules/gulp-zip/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/gulp-zip/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/gulplog": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", + "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "glogg": "^2.2.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/homedir-polyfill": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", + "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-passwd": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-10.2.2.tgz", + "integrity": "sha512-tyao/4Vo36XnUItZ7DnUXX4f1jVao2mSrleV/5IPtW/XAEA26hRVsbc68nuTEKWcr5vMP/1mVoT2O7u8H4v1Vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^9.1.0", + "@inquirer/prompts": "^5.5.0", + "@inquirer/type": "^1.5.3", + "@types/mute-stream": "^0.0.4", + "ansi-escapes": "^4.3.2", + "mute-stream": "^1.0.0", + "run-async": "^3.0.0", + "rxjs": "^7.8.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/interpret": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", + "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/is-absolute": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", + "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-relative": "^1.0.0", + "is-windows": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-builtin-module": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", + "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", + "dev": true, + "license": "MIT", + "dependencies": { + "builtin-modules": "^3.3.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-negated-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", + "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-relative": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", + "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unc-path": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unc-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", + "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "unc-path-regex": "^0.1.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-valid-glob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", + "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istextorbinary": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", + "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "binaryextensions": "^2.2.0", + "textextensions": "^3.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-canvas-mock": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.5.0.tgz", + "integrity": "sha512-s2bmY2f22WPMzhB2YA93kiyf7CAfWAnV/sFfY9s48IVOrGmwui1eSFluDPesq1M+7tSC1hJAit6mzO0ZNXvVBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssfontparser": "^1.2.1", + "moo-color": "^1.0.2" + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-cli/node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-cli/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-jsdom": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", + "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/jsdom": "^20.0.0", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0", + "jsdom": "^20.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jscodeshift": { + "version": "0.16.1", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.16.1.tgz", + "integrity": "sha512-oMQXySazy63awNBzMpXbbVv73u3irdxTeX2L5ueRyFRxi32qb9uzdZdOY5fTBYADBG19l5M/wnGknZSV1dzCdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.7", + "@babel/parser": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/preset-flow": "^7.24.7", + "@babel/preset-typescript": "^7.24.7", + "@babel/register": "^7.24.6", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.7", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.23.9", + "temp": "^0.9.4", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + }, + "peerDependenciesMeta": { + "@babel/preset-env": { + "optional": true + } + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/jsdom": { + "version": "20.0.3", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", + "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "abab": "^2.0.6", + "acorn": "^8.8.1", + "acorn-globals": "^7.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.2", + "decimal.js": "^10.4.2", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.1", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.2", + "parse5": "^7.1.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.1.2", + "w3c-xmlserializer": "^4.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0", + "ws": "^8.11.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jspsych": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/jspsych/-/jspsych-8.2.1.tgz", + "integrity": "sha512-VpAxmkzj72/Y2eTKXt5ksZGFL7VqvgUL+66pUqm7KPUSZsIOSM1rKCtMN42u3fT2N8EonR7LGFFcsAvnZAOykQ==", + "license": "MIT", + "dependencies": { + "auto-bind": "^4.0.0", + "random-words": "^1.1.1", + "seedrandom": "^3.0.5", + "type-fest": "^2.9.0" + } + }, + "node_modules/jspsych/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/last-run": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", + "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/lead": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", + "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/liftoff": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.1.tgz", + "integrity": "sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "extend": "^3.0.2", + "findup-sync": "^5.0.0", + "fined": "^2.0.0", + "flagged-respawn": "^2.0.0", + "is-plain-object": "^5.0.0", + "rechoir": "^0.8.0", + "resolve": "^1.20.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/load-tsconfig": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", + "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/module-alias": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/moo-color": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", + "integrity": "sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.1.4" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mute-stdout": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", + "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/now-and-later": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", + "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nwsapi": { + "version": "2.2.20", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", + "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==", + "dev": true, + "license": "MIT" + }, + "node_modules/object.defaults": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", + "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-each": "^1.0.1", + "array-slice": "^1.0.0", + "for-own": "^1.0.0", + "isobject": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ospec": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/ospec/-/ospec-3.1.0.tgz", + "integrity": "sha512-+nGtjV3vlADp+UGfL51miAh/hB4awPBkQrArhcgG4trAaoA2gKt5bf9w0m9ch9zOr555cHWaCHZEDiBOkNZSxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "ospec": "bin/ospec" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/parse-filepath": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", + "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-absolute": "^1.0.0", + "map-cache": "^0.2.0", + "path-root": "^0.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-passwd": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", + "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-root": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", + "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-root-regex": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-root-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", + "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/random-words": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/random-words/-/random-words-1.3.0.tgz", + "integrity": "sha512-brwCGe+DN9DqZrAQVNj1Tct1Lody6GrYL/7uei5wfjeQdacFyFd2h/51LNlOoBMzIKMS9xohuL4+wlF/z1g/xg==", + "license": "MIT", + "dependencies": { + "seedrandom": "^3.0.5" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/recast": { + "version": "0.23.11", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", + "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ast-types": "^0.16.1", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tiny-invariant": "^1.3.3", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", + "dev": true, + "license": "ISC" + }, + "node_modules/replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/replace-homedir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", + "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/replacestream": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", + "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "escape-string-regexp": "^1.0.3", + "object-assign": "^4.0.1", + "readable-stream": "^2.0.2" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-dir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", + "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "expand-tilde": "^2.0.0", + "global-modules": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-options": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", + "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", + "dev": true, + "license": "MIT", + "dependencies": { + "value-or-function": "^4.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/rollup": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", + "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.21.2", + "@rollup/rollup-android-arm64": "4.21.2", + "@rollup/rollup-darwin-arm64": "4.21.2", + "@rollup/rollup-darwin-x64": "4.21.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", + "@rollup/rollup-linux-arm-musleabihf": "4.21.2", + "@rollup/rollup-linux-arm64-gnu": "4.21.2", + "@rollup/rollup-linux-arm64-musl": "4.21.2", + "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", + "@rollup/rollup-linux-riscv64-gnu": "4.21.2", + "@rollup/rollup-linux-s390x-gnu": "4.21.2", + "@rollup/rollup-linux-x64-gnu": "4.21.2", + "@rollup/rollup-linux-x64-musl": "4.21.2", + "@rollup/rollup-win32-arm64-msvc": "4.21.2", + "@rollup/rollup-win32-ia32-msvc": "4.21.2", + "@rollup/rollup-win32-x64-msvc": "4.21.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup-plugin-dts": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.1.tgz", + "integrity": "sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==", + "dev": true, + "license": "LGPL-3.0-only", + "dependencies": { + "magic-string": "^0.30.10" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/Swatinem" + }, + "optionalDependencies": { + "@babel/code-frame": "^7.24.2" + }, + "peerDependencies": { + "rollup": "^3.29.4 || ^4", + "typescript": "^4.5 || ^5.0" + } + }, + "node_modules/rollup-plugin-esbuild": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-6.1.1.tgz", + "integrity": "sha512-CehMY9FAqJD5OUaE/Mi1r5z0kNeYxItmRO2zG4Qnv2qWKF09J2lTy5GUzjJR354ZPrLkCj4fiBN41lo8PzBUhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.5", + "debug": "^4.3.4", + "es-module-lexer": "^1.3.1", + "get-tsconfig": "^4.7.2" + }, + "engines": { + "node": ">=14.18.0" + }, + "peerDependencies": { + "esbuild": ">=0.18.0", + "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" + } + }, + "node_modules/rollup-plugin-modify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-modify/-/rollup-plugin-modify-3.0.0.tgz", + "integrity": "sha512-p/ffs0Y2jz2dEnWjq1oVC7SY37tuS+aP7whoNaQz1EAAOPg+k3vKJo8cMMWx6xpdd0NzhX4y2YF9o/NPu5YR0Q==", + "dev": true, + "license": "WTFPL", + "dependencies": { + "magic-string": "0.25.2", + "ospec": "3.1.0" + } + }, + "node_modules/rollup-plugin-modify/node_modules/magic-string": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.4" + } + }, + "node_modules/rollup-plugin-node-externals": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-externals/-/rollup-plugin-node-externals-7.1.3.tgz", + "integrity": "sha512-RM+7tJAejAoRsCf93TptTSdqUhRA8S78DleihMiu54Kac+uLkd9VIegLPhGnaW3ehZTXh56+R301mFH6j2A7vw==", + "dev": true, + "funding": [ + { + "type": "patreon", + "url": "https://patreon.com/Septh" + }, + { + "type": "paypal", + "url": "https://paypal.me/septh07" + } + ], + "license": "MIT", + "engines": { + "node": ">= 21 || ^20.6.0 || ^18.19.0" + }, + "peerDependencies": { + "rollup": "^3.0.0 || ^4.0.0" + } + }, + "node_modules/rollup/node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/seedrandom": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", + "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/semver-greatest-satisfied-range": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", + "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "sver": "^1.8.3" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "license": "MIT", + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, + "node_modules/sparkles": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", + "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/stream-composer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", + "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.13.2" + } + }, + "node_modules/stream-exhaust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", + "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/streamx": { + "version": "2.22.1", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", + "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.34.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", + "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sver": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", + "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", + "dev": true, + "license": "MIT", + "optionalDependencies": { + "semver": "^6.3.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/sync-fetch": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.4.5.tgz", + "integrity": "sha512-esiWJ7ixSKGpd9DJPBTC4ckChqdOjIwJfYhVHkcQ2Gnm41323p1TRmEI+esTQ9ppD+b5opps2OTEGTCGX5kF+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.7.1", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/teex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", + "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + } + }, + "node_modules/temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/textextensions": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", + "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://bevry.me/fund" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/through2": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", + "integrity": "sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "readable-stream": "~1.0.17", + "xtend": "~2.1.1" + } + }, + "node_modules/through2/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/through2/node_modules/readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/through2/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "os-tmpdir": "~1.0.2" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-through": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", + "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "streamx": "^2.12.5" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tough-cookie": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", + "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsup": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", + "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^4.0.0", + "cac": "^6.7.12", + "chokidar": "^3.5.1", + "debug": "^4.3.1", + "esbuild": "^0.17.6", + "execa": "^5.0.0", + "globby": "^11.0.3", + "joycon": "^3.0.1", + "postcss-load-config": "^3.0.1", + "resolve-from": "^5.0.0", + "rollup": "^3.2.5", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.20.3", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/tsup/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "node_modules/tsup/node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tsup/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/tsup/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tsup/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/tsup/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.8.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", + "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/unc-path-regex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", + "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/undertaker": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", + "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bach": "^2.0.1", + "fast-levenshtein": "^3.0.0", + "last-run": "^2.0.0", + "undertaker-registry": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/undertaker-registry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", + "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8flags": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", + "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/value-or-function": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", + "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/vinyl-contents": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", + "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^5.0.0", + "vinyl": "^3.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-contents/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-contents/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", + "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-mkdirp-stream": "^2.0.1", + "glob-stream": "^8.0.3", + "graceful-fs": "^4.2.11", + "iconv-lite": "^0.6.3", + "is-valid-glob": "^1.0.0", + "lead": "^4.0.0", + "normalize-path": "3.0.0", + "resolve-options": "^2.0.0", + "stream-composer": "^1.0.2", + "streamx": "^2.14.0", + "to-through": "^3.0.0", + "value-or-function": "^4.0.0", + "vinyl": "^3.0.1", + "vinyl-sourcemap": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-fs/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vinyl-fs/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-fs/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", + "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-source-map": "^2.0.0", + "graceful-fs": "^4.2.10", + "now-and-later": "^3.0.0", + "streamx": "^2.12.5", + "vinyl": "^3.0.0", + "vinyl-contents": "^2.0.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vinyl-sourcemap/node_modules/replace-ext": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", + "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/vinyl-sourcemap/node_modules/vinyl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", + "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clone": "^2.1.2", + "remove-trailing-separator": "^1.1.0", + "replace-ext": "^2.0.0", + "teex": "^1.0.1" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/vue-jscodeshift-adapter": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vue-jscodeshift-adapter/-/vue-jscodeshift-adapter-2.2.1.tgz", + "integrity": "sha512-4aTkHYknYgP9uk/465MDZjvrotF6o2RMWDy0t+9RUULfgbkT+rHLrNw8onxOk4Y8fCpgcS81b09afodRZY/LuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue-sfc-descriptor-to-string": "^1.0.0", + "vue-template-compiler": "^2.5.13" + } + }, + "node_modules/vue-sfc-descriptor-to-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vue-sfc-descriptor-to-string/-/vue-sfc-descriptor-to-string-1.0.0.tgz", + "integrity": "sha512-VYNMsrIPZQZau5Gk8IVtgonN1quOznP9/pLIF5m2c4R30KCDDe3NwthrsM7lSUY2K4lezcb8j3Wu8cQhBuZEMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^3.2.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", + "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/w3c-xmlserializer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", + "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/write-file-atomic/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xtend": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", + "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", + "dev": true, + "dependencies": { + "object-keys": "~0.4.0" + }, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs/node_modules/yargs-parser": { + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/packages/spatial-nback-task/package.json b/packages/spatial-nback-task/package.json new file mode 100644 index 00000000..e7d2dbf3 --- /dev/null +++ b/packages/spatial-nback-task/package.json @@ -0,0 +1,39 @@ +{ + "name": "spatial-nback-task", + "version": "0.0.1", + "description": "Deployable spatial nback timeline", + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.ts", + "unpkg": "dist/index.browser.min.js", + "scripts": { + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpatialNbackTask" + }, + "repository": { + "type": "git", + "url": "https://github.com/ahfarhat/JsPsychTemplates/", + "directory": "" + }, + "keywords": [ + "jsPsych" + ], + "author": { + "name": "Hunter Farhat", + "url": "'use strict';https://github.com/ahfarhat" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/ahfarhat/JsPsychTemplates//issues" + }, + "homepage": "/spatial-nback-task/README.md", + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + }, + "dependencies": { + "@jspsych/plugin-html-keyboard-response": "^2.1.0", + "jsPsych": "npm:jspsych@^8.2.1" + } +} diff --git a/packages/spatial-nback-task/src/index copy.ts b/packages/spatial-nback-task/src/index copy.ts new file mode 100644 index 00000000..6d321514 --- /dev/null +++ b/packages/spatial-nback-task/src/index copy.ts @@ -0,0 +1,108 @@ +import { initJsPsych, JsPsych } from "jspsych" +import jsPsychPluginSpatialNbackTs from "../../plugin-spatial-nback-ts"; +import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; + +// Remove this - don't initialize jsPsych here, let the consumer do it +const jsPsych = initJsPsych(); + + +const task_instructions = { + type: jsPsychHtmlKeyboardResponse, + response_ends_trial: true, + on_load: function() { + document.body.addEventListener('pointerdown', function endTrialOnClick() { + jsPsych.finishTrial(); + document.body.removeEventListener('pointerdown', endTrialOnClick); + }); + }, + stimulus: ` +
+

Spatial N-Back Task

+

In this task, you will see a grid with blue squares appearing in different positions.

+

Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.

+

Try to respond as quickly and accurately as possible.

+

Press any key or tap anywhere to begin the task.

+
+ `, + choices: "ALL_KEYS" +}; + +export function createTimeline(jsPsych: JsPsych, { + rows = 3, + cols = 3, + n_back_level = 1, + total_trials = 6, + target_percentage = 25, + stimulus_duration = 750, + isi_duration = 250, + feedback_duration = 1000, + show_feedback = false, + show_feedback_border = false, + progress_bar = true, + showFeedbackNoResponse = true, + cell_size = 80, + instructions = "Click MATCH when the position matches the one from {n} trial(s) ago", + button_text = "MATCH", + stimulus_color = "#2196F3", + correct_color = "#4CAF50", + incorrect_color = "#F44336", +}: { + rows?: number, + cols?: number, + n_back_level?: number, + total_trials?: number, + target_percentage?: number, + stimulus_duration?: number, + isi_duration?: number, + feedback_duration?: number, + show_feedback?: boolean, + show_feedback_border?: boolean, + progress_bar?: boolean, + showFeedbackNoResponse?: boolean, + cell_size?: number, + instructions?: string, + button_text?: string, + stimulus_color?: string, + correct_color?: string, + incorrect_color?: string, +} = {}) { + + // Create the actual spatial n-back task trial + const spatial_nback_task = { + type: jsPsychPluginSpatialNbackTs, + rows: rows, + cols: cols, + n_back_level: n_back_level, + total_trials: total_trials, + target_percentage: target_percentage, + stimulus_duration: stimulus_duration, + isi_duration: isi_duration, + feedback_duration: feedback_duration, + show_feedback: show_feedback, + show_feedback_border: show_feedback_border, + show_progress: progress_bar, + showFeedbackNoResponse: showFeedbackNoResponse, + cell_size: cell_size, + instructions: instructions, + button_text: button_text, + stimulus_color: stimulus_color, + correct_color: correct_color, + incorrect_color: incorrect_color, + }; + + // Return the complete timeline + return { + timeline: [task_instructions, spatial_nback_task] + }; +} + +// Export the timeline units for modular use +export const timelineUnits = { + instructions: task_instructions, + createTask: (params: any) => ({ + type: jsPsychPluginSpatialNbackTs, + ...params + }) +}; + +export const utils = { task_instructions }; diff --git a/packages/spatial-nback-task/src/index.ts b/packages/spatial-nback-task/src/index.ts new file mode 100644 index 00000000..dc75c3f5 --- /dev/null +++ b/packages/spatial-nback-task/src/index.ts @@ -0,0 +1,276 @@ +import { initJsPsych, JsPsych } from "jspsych" +import jsPsychPluginSpatialNbackTs from "@jspsych/plugin-spatial-nback-ts"; +import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; + +const task_instructions = { + type: jsPsychHtmlKeyboardResponse, + response_ends_trial: true, + on_load: function() { + document.body.addEventListener('pointerdown', function endTrialOnClick() { + // @ts-ignore - jsPsych is available in the global scope during trial execution + jsPsych.finishTrial(); + document.body.removeEventListener('pointerdown', endTrialOnClick); + }); + }, + stimulus: ` +
+

Spatial N-Back Task

+

In this task, you will see a grid with blue squares appearing in different positions.

+

Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.

+

Try to respond as quickly and accurately as possible.

+

Press any key or tap anywhere to begin the task.

+
+ `, + choices: "ALL_KEYS" +}; + +// Generate stimulus sequence for n-back task +function generateNBackSequence(total_trials: number, n_back_level: number, target_percentage: number, rows: number, cols: number) { + const positions: Array<{row: number, col: number}> = []; + const is_target: boolean[] = []; + + // Generate first n trials (cannot be targets) + for (let i = 0; i < n_back_level; i++) { + positions.push({ + row: Math.floor(Math.random() * rows), + col: Math.floor(Math.random() * cols) + }); + is_target.push(false); + } + + // Calculate number of targets to place + const n_targets = Math.round((target_percentage / 100) * (total_trials - n_back_level)); + let targets_placed = 0; + + // Generate remaining trials with targets + for (let i = n_back_level; i < total_trials; i++) { + const can_be_target = targets_placed < n_targets; + const should_be_target = can_be_target && Math.random() < 0.5; + + if (should_be_target) { + // Make this a target trial (same position as n trials back) + positions.push({ + row: positions[i - n_back_level].row, + col: positions[i - n_back_level].col + }); + is_target.push(true); + targets_placed++; + } else { + // Generate non-target position + let new_position: {row: number, col: number}; + do { + new_position = { + row: Math.floor(Math.random() * rows), + col: Math.floor(Math.random() * cols) + }; + } while ( + new_position.row === positions[i - n_back_level].row && + new_position.col === positions[i - n_back_level].col + ); + positions.push(new_position); + is_target.push(false); + } + } + + return { positions, is_target }; +} + +export function createSpatialNBackTimeline({ + rows = 3, + cols = 3, + n_back_level = 1, + total_trials = 20, + target_percentage = 25, + stimulus_duration = 750, + isi_duration = 250, + feedback_duration = 1000, + show_feedback = false, + show_feedback_border = false, + showFeedbackNoResponse = false, + feedbackWaitNoResponse = true, + cell_size = 150, + instructions_template = "Click the button when the position matches the one from {n} trial(s) ago", + button_text = "MATCH", + stimulus_color = "#2196F3", + correct_color = "#4CAF50", + incorrect_color = "#F44336", + include_instructions = false, + randomize_trials = false, +}: { + rows?: number, + cols?: number, + n_back_level?: number, + total_trials?: number, + target_percentage?: number, + stimulus_duration?: number, + isi_duration?: number, + feedback_duration?: number, + show_feedback?: boolean, + show_feedback_border?: boolean, + showFeedbackNoResponse?: boolean, + feedbackWaitNoResponse?: boolean, + cell_size?: number, + instructions_template?: string, + button_text?: string, + stimulus_color?: string, + correct_color?: string, + incorrect_color?: string, + include_instructions?: boolean, + randomize_trials?: boolean, +} = {}) { + + // Generate the sequence + const sequence = generateNBackSequence(total_trials, n_back_level, target_percentage, rows, cols); + + // Create individual trial objects + const trials = []; + for (let i = 0; i < total_trials; i++) { + const trial_instructions = instructions_template + .replace('{n}', n_back_level.toString()) + .replace('{trial}', (i + 1).toString()) + .replace('{total}', total_trials.toString()); + + trials.push({ + type: jsPsychPluginSpatialNbackTs, + rows: rows, + cols: cols, + stimulus_row: sequence.positions[i].row, + stimulus_col: sequence.positions[i].col, + is_target: sequence.is_target[i], + stimulus_duration: stimulus_duration, + isi_duration: isi_duration, + feedback_duration: feedback_duration, + show_feedback: show_feedback, + show_feedback_border: show_feedback_border, + showFeedbackNoResponse: showFeedbackNoResponse, + feedbackWaitNoResponse: feedbackWaitNoResponse, + cell_size: cell_size, + instructions: trial_instructions, + button_text: button_text, + stimulus_color: stimulus_color, + correct_color: correct_color, + incorrect_color: incorrect_color, + data: { + trial_number: i + 1, + n_back_level: n_back_level, + total_trials: total_trials, + task: 'spatial-nback' + } + }); + } + + // Create the main task timeline + const task_timeline = { + timeline: trials, + randomize_order: randomize_trials + }; + + // Return complete timeline with or without instructions + if (include_instructions) { + // Update instructions to show current n-back level + const custom_instructions = { + ...task_instructions, + stimulus: task_instructions.stimulus.replace( + '1 trial ago', + `${n_back_level} trial${n_back_level > 1 ? 's' : ''} ago` + ) + }; + + return { + timeline: [custom_instructions, task_timeline] + }; + } else { + return task_timeline; + } +} + +// Create a practice timeline with fewer trials +export function createPracticeTimeline(options: Parameters[0] = {}) { + return createSpatialNBackTimeline({ + ...options, + total_trials: 6, + target_percentage: 33, + show_feedback: true, + show_feedback_border: true, + include_instructions: true + }); +} + +// Create multiple n-back levels timeline +export function createMultiLevelNBackTimeline({ + n_back_levels = [1, 2], + trials_per_level = 20, + randomize_levels = false, + ...sharedOptions +}: { + n_back_levels?: number[], + trials_per_level?: number, + randomize_levels?: boolean, +} & Parameters[0] = {}) { + + const level_timelines = n_back_levels.map(level => { + return createSpatialNBackTimeline({ + ...sharedOptions, + n_back_level: level, + total_trials: trials_per_level, + include_instructions: false + }); + }); + + return { + timeline: level_timelines, + randomize_order: randomize_levels + }; +} + +// Utility functions for common configurations +export const presetConfigurations = { + easy: () => createSpatialNBackTimeline({ + n_back_level: 1, + total_trials: 20, + target_percentage: 30, + show_feedback: true + }), + + medium: () => createSpatialNBackTimeline({ + n_back_level: 2, + total_trials: 30, + target_percentage: 25, + show_feedback: false + }), + + hard: () => createSpatialNBackTimeline({ + n_back_level: 3, + total_trials: 40, + target_percentage: 20, + show_feedback: false, + rows: 4, + cols: 4 + }), + + research: () => createMultiLevelNBackTimeline({ + n_back_levels: [1, 2, 3], + trials_per_level: 50, + target_percentage: 25, + show_feedback: false, + randomize_levels: true + }) +}; + +// Export individual components for custom use +export { task_instructions, generateNBackSequence }; + +// Export default timeline creator +export default createSpatialNBackTimeline; + +export const timelineUnits = { + createPracticeTimeline, + createSpatialNBackTimeline, + createMultiLevelNBackTimeline, +}; + +export const utils = { + presetConfigurations, + generateNBackSequence, + task_instructions +} \ No newline at end of file diff --git a/packages/spatial-nback-task/tsconfig.json b/packages/spatial-nback-task/tsconfig.json new file mode 100644 index 00000000..33655f70 --- /dev/null +++ b/packages/spatial-nback-task/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@jspsych/config/tsconfig.contrib.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": [ + "src", + "../cli/src/build.js" + ] +} \ No newline at end of file From 15aae88b5f725f719ad68fd3ee971408226fa192 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 9 Jun 2025 11:29:47 -0400 Subject: [PATCH 02/37] edited some params --- packages/spatial-nback-task/examples/index.html | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/spatial-nback-task/examples/index.html b/packages/spatial-nback-task/examples/index.html index f9f951e7..b28b95b7 100644 --- a/packages/spatial-nback-task/examples/index.html +++ b/packages/spatial-nback-task/examples/index.html @@ -13,19 +13,22 @@ \ No newline at end of file From e6ade3d1d1944f03e04413ece3d0fb36be04873f Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 9 Jun 2025 12:49:40 -0400 Subject: [PATCH 03/37] added ignore file --- packages/spatial-nback-task/.gitignore | 47 ++++++++ packages/spatial-nback-task/src/index copy.ts | 108 ------------------ 2 files changed, 47 insertions(+), 108 deletions(-) create mode 100644 packages/spatial-nback-task/.gitignore delete mode 100644 packages/spatial-nback-task/src/index copy.ts diff --git a/packages/spatial-nback-task/.gitignore b/packages/spatial-nback-task/.gitignore new file mode 100644 index 00000000..1632fe5c --- /dev/null +++ b/packages/spatial-nback-task/.gitignore @@ -0,0 +1,47 @@ +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +package-lock.json +.pnpm-debug.log + +# Build artifacts +dist/ +build/ +.cache/ +.temp/ +out/ +coverage/ +*.tsbuildinfo + +# OS files +.DS_Store +Thumbs.db + +# Environment files +.env +.env.local +.env.*.local + +# IDEs and editors +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln + +# Misc +logs/ +*.log +*.bak +*.swp + +# macOS Finder files +.AppleDouble +.LSOverride + +# npm pack artifacts +*.tgz \ No newline at end of file diff --git a/packages/spatial-nback-task/src/index copy.ts b/packages/spatial-nback-task/src/index copy.ts deleted file mode 100644 index 6d321514..00000000 --- a/packages/spatial-nback-task/src/index copy.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { initJsPsych, JsPsych } from "jspsych" -import jsPsychPluginSpatialNbackTs from "../../plugin-spatial-nback-ts"; -import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; - -// Remove this - don't initialize jsPsych here, let the consumer do it -const jsPsych = initJsPsych(); - - -const task_instructions = { - type: jsPsychHtmlKeyboardResponse, - response_ends_trial: true, - on_load: function() { - document.body.addEventListener('pointerdown', function endTrialOnClick() { - jsPsych.finishTrial(); - document.body.removeEventListener('pointerdown', endTrialOnClick); - }); - }, - stimulus: ` -
-

Spatial N-Back Task

-

In this task, you will see a grid with blue squares appearing in different positions.

-

Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.

-

Try to respond as quickly and accurately as possible.

-

Press any key or tap anywhere to begin the task.

-
- `, - choices: "ALL_KEYS" -}; - -export function createTimeline(jsPsych: JsPsych, { - rows = 3, - cols = 3, - n_back_level = 1, - total_trials = 6, - target_percentage = 25, - stimulus_duration = 750, - isi_duration = 250, - feedback_duration = 1000, - show_feedback = false, - show_feedback_border = false, - progress_bar = true, - showFeedbackNoResponse = true, - cell_size = 80, - instructions = "Click MATCH when the position matches the one from {n} trial(s) ago", - button_text = "MATCH", - stimulus_color = "#2196F3", - correct_color = "#4CAF50", - incorrect_color = "#F44336", -}: { - rows?: number, - cols?: number, - n_back_level?: number, - total_trials?: number, - target_percentage?: number, - stimulus_duration?: number, - isi_duration?: number, - feedback_duration?: number, - show_feedback?: boolean, - show_feedback_border?: boolean, - progress_bar?: boolean, - showFeedbackNoResponse?: boolean, - cell_size?: number, - instructions?: string, - button_text?: string, - stimulus_color?: string, - correct_color?: string, - incorrect_color?: string, -} = {}) { - - // Create the actual spatial n-back task trial - const spatial_nback_task = { - type: jsPsychPluginSpatialNbackTs, - rows: rows, - cols: cols, - n_back_level: n_back_level, - total_trials: total_trials, - target_percentage: target_percentage, - stimulus_duration: stimulus_duration, - isi_duration: isi_duration, - feedback_duration: feedback_duration, - show_feedback: show_feedback, - show_feedback_border: show_feedback_border, - show_progress: progress_bar, - showFeedbackNoResponse: showFeedbackNoResponse, - cell_size: cell_size, - instructions: instructions, - button_text: button_text, - stimulus_color: stimulus_color, - correct_color: correct_color, - incorrect_color: incorrect_color, - }; - - // Return the complete timeline - return { - timeline: [task_instructions, spatial_nback_task] - }; -} - -// Export the timeline units for modular use -export const timelineUnits = { - instructions: task_instructions, - createTask: (params: any) => ({ - type: jsPsychPluginSpatialNbackTs, - ...params - }) -}; - -export const utils = { task_instructions }; From 37d585660d689325222bfccc2963c2e716d5ba7c Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 9 Jun 2025 16:27:22 -0400 Subject: [PATCH 04/37] button response for task_instrucitons and import updates --- package-lock.json | 10 ++++++++++ packages/spatial-nback-task/package.json | 1 + packages/spatial-nback-task/src/index.ts | 15 ++++----------- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index a7b8d300..3b4a3e32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2573,6 +2573,15 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true }, + "node_modules/@jspsych/plugin-html-button-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-button-response/-/plugin-html-button-response-2.1.0.tgz", + "integrity": "sha512-AhJPePA8s/ount+QYtqfgMtEkz7g+j+bnhQ9XqxfsnK2f5MdnlakYtpjxW9YgFx9y0Gs7yb7OZk1+omcKlzaEQ==", + "license": "MIT", + "peerDependencies": { + "jspsych": ">=7.1.0" + } + }, "node_modules/@jspsych/plugin-html-keyboard-response": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", @@ -11678,6 +11687,7 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "@jspsych/plugin-html-button-response": "^2.1.0", "@jspsych/plugin-html-keyboard-response": "^2.1.0", "jsPsych": "npm:jspsych@^8.2.1" }, diff --git a/packages/spatial-nback-task/package.json b/packages/spatial-nback-task/package.json index e7d2dbf3..562e2a88 100644 --- a/packages/spatial-nback-task/package.json +++ b/packages/spatial-nback-task/package.json @@ -33,6 +33,7 @@ "typescript": "^5.0.2" }, "dependencies": { + "@jspsych/plugin-html-button-response": "^2.1.0", "@jspsych/plugin-html-keyboard-response": "^2.1.0", "jsPsych": "npm:jspsych@^8.2.1" } diff --git a/packages/spatial-nback-task/src/index.ts b/packages/spatial-nback-task/src/index.ts index dc75c3f5..a55e9ce7 100644 --- a/packages/spatial-nback-task/src/index.ts +++ b/packages/spatial-nback-task/src/index.ts @@ -1,27 +1,20 @@ import { initJsPsych, JsPsych } from "jspsych" import jsPsychPluginSpatialNbackTs from "@jspsych/plugin-spatial-nback-ts"; import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; +import jsPsychHtmlButtonResponse from "@jspsych/plugin-html-button-response"; const task_instructions = { - type: jsPsychHtmlKeyboardResponse, - response_ends_trial: true, - on_load: function() { - document.body.addEventListener('pointerdown', function endTrialOnClick() { - // @ts-ignore - jsPsych is available in the global scope during trial execution - jsPsych.finishTrial(); - document.body.removeEventListener('pointerdown', endTrialOnClick); - }); - }, + type: jsPsychHtmlButtonResponse, stimulus: `

Spatial N-Back Task

In this task, you will see a grid with blue squares appearing in different positions.

Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.

Try to respond as quickly and accurately as possible.

-

Press any key or tap anywhere to begin the task.

+

Click the button below to begin the task.

`, - choices: "ALL_KEYS" + choices: ['Continue'], }; // Generate stimulus sequence for n-back task From dbcb8bdca6b525fa9208eb99166185844459010f Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Thu, 12 Jun 2025 12:34:22 -0400 Subject: [PATCH 05/37] added docs --- packages/spatial-nback/.gitignore | 47 ++++ packages/spatial-nback/README.md | 36 +++ packages/spatial-nback/docs/spatial-nback.md | 98 +++++++ packages/spatial-nback/examples/index.html | 62 +++++ packages/spatial-nback/jest.config.js | 1 + packages/spatial-nback/package.json | 41 +++ packages/spatial-nback/src/index.ts | 269 +++++++++++++++++++ packages/spatial-nback/tsconfig.json | 10 + 8 files changed, 564 insertions(+) create mode 100644 packages/spatial-nback/.gitignore create mode 100644 packages/spatial-nback/README.md create mode 100644 packages/spatial-nback/docs/spatial-nback.md create mode 100644 packages/spatial-nback/examples/index.html create mode 100644 packages/spatial-nback/jest.config.js create mode 100644 packages/spatial-nback/package.json create mode 100644 packages/spatial-nback/src/index.ts create mode 100644 packages/spatial-nback/tsconfig.json diff --git a/packages/spatial-nback/.gitignore b/packages/spatial-nback/.gitignore new file mode 100644 index 00000000..1632fe5c --- /dev/null +++ b/packages/spatial-nback/.gitignore @@ -0,0 +1,47 @@ +# Node.js +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +package-lock.json +.pnpm-debug.log + +# Build artifacts +dist/ +build/ +.cache/ +.temp/ +out/ +coverage/ +*.tsbuildinfo + +# OS files +.DS_Store +Thumbs.db + +# Environment files +.env +.env.local +.env.*.local + +# IDEs and editors +.vscode/ +.idea/ +*.suo +*.ntvs* +*.njsproj +*.sln + +# Misc +logs/ +*.log +*.bak +*.swp + +# macOS Finder files +.AppleDouble +.LSOverride + +# npm pack artifacts +*.tgz \ No newline at end of file diff --git a/packages/spatial-nback/README.md b/packages/spatial-nback/README.md new file mode 100644 index 00000000..a2159f76 --- /dev/null +++ b/packages/spatial-nback/README.md @@ -0,0 +1,36 @@ +# spatial-nback + +## Overview + +Deployable spatial nback timeline + +## Loading + +*Enter instructions for loading the timeline package here.* + +## Compatibility + +`spatial-nback` requires jsPsych v8.0.0 or later. + +## Documentation + +### createTimeline + +#### jsPsychTimelineSpatialNback.createTimeline(jsPsych, { *options* }) ⇒ timeline +Description of the timeline that this plugin generates + +The following parameters can be specified in the **options** parameter. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| parameter | type | default | description | + + +### timelineUnits + + +### utils + +## Author / Citation + +Hunter Farhat \ No newline at end of file diff --git a/packages/spatial-nback/docs/spatial-nback.md b/packages/spatial-nback/docs/spatial-nback.md new file mode 100644 index 00000000..0c1520d7 --- /dev/null +++ b/packages/spatial-nback/docs/spatial-nback.md @@ -0,0 +1,98 @@ +# spatial-nback + +A deployable spatial n-back timeline for jsPsych. This module provides a flexible implementation of the spatial n-back task, supporting custom grid sizes, n-back levels, feedback, and more. + +## Parameters + +### Initialization Parameters + +Initialization parameters can be set when calling `initJsPsych()`: + +```js +initJsPsych({ + timelines: [ + {type: jsPsychTimelineSpatialNback, params: {...}} + ] +}) +``` + +Parameter | Type | Default Value | Description +----------|------|---------------|------------ +rows | number | 3 | Number of rows in the grid +cols | number | 3 | Number of columns in the grid +n_back_level | number | 1 | The n-back level (how many trials back to match) +total_trials | number | 20 | Total number of trials in the timeline +target_percentage | number | 25 | Percentage of trials that are targets +stimulus_duration | number | 750 | Duration (ms) the stimulus is shown +isi_duration | number | 250 | Inter-stimulus interval (ms) +feedback_duration | number | 1000 | Duration (ms) of feedback display +show_feedback | boolean | false | Whether to show feedback after each trial +show_feedback_border | boolean | false | Whether to highlight the grid border for feedback +showFeedbackNoResponse | boolean | false | Show feedback if no response is made +feedbackWaitNoResponse | boolean | true | Wait for feedback even if no response +cell_size | number | 150 | Size (px) of each grid cell +instructions_trial | string | "Click the button when the position matches the one from {n} trial(s) ago" | Instructions template for each trial +button_text | string | "MATCH" | Text for the match button +stimulus_color | string | "#2196F3" | Color of the stimulus square +correct_color | string | "#4CAF50" | Color for correct feedback +incorrect_color | string | "#F44336" | Color for incorrect feedback +include_instructions | boolean | false | Whether to include instructions at the start +randomize_trials | boolean | false | Randomize the order of trials + +### Trial Parameters + +Trial parameters can be set when adding the timeline to a trial object: + +```js +var trial = { + type: jsPsych..., // your jsPsych plugin type + timelines: [ + {type: jsPsychTimelineSpatialNback, params: {...}} + ] +} +``` + +Parameter | Type | Default Value | Description +----------|------|---------------|------------ +See above | | | (All initialization parameters can also be set per trial) + +## Data Generated + +Name | Type | Value +-----|------|------ +trial_number | number | The trial index (1-based) +n_back_level | number | The n-back level for the timeline +total_trials | number | Total number of trials in the timeline +task | string | "spatial-nback" +response | string/number | Participant's response (if applicable) +rt | number | Response time in ms + +## Functions + +The following functions/utilities are exported for custom use: + +### createSpatialNBackTimeline(params) +Creates a spatial n-back timeline with the specified parameters. + +### createPracticeTimeline(params) +Creates a short practice timeline (6 trials, 33% targets, feedback enabled). + +### createMultiLevelNBackTimeline(params) +Creates a timeline with multiple n-back levels (e.g., 1-back, 2-back, 3-back). + +### presetConfigurations +Predefined configurations: +- `easy()` +- `medium()` +- `hard()` +- `research()` + +### generateNBackSequence(total_trials, n_back_level, target_percentage, rows, cols) +Generates the sequence of positions and target flags for the task. + +### instrictions_template +Default instructions template object. + +--- + +See [src/index.ts](src/index.ts) for implementation details. \ No newline at end of file diff --git a/packages/spatial-nback/examples/index.html b/packages/spatial-nback/examples/index.html new file mode 100644 index 00000000..6965da68 --- /dev/null +++ b/packages/spatial-nback/examples/index.html @@ -0,0 +1,62 @@ + + + + + Spatial N-Back Task + + + + + + + + + + + \ No newline at end of file diff --git a/packages/spatial-nback/jest.config.js b/packages/spatial-nback/jest.config.js new file mode 100644 index 00000000..bd1aacef --- /dev/null +++ b/packages/spatial-nback/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/spatial-nback/package.json b/packages/spatial-nback/package.json new file mode 100644 index 00000000..9c101719 --- /dev/null +++ b/packages/spatial-nback/package.json @@ -0,0 +1,41 @@ +{ + "name": "@jspsych-timelines/spatial-nback", + "version": "0.0.2", + "description": "spatial nback task timeline for jsPsych", + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.ts", + "unpkg": "dist/index.browser.min.js", + "scripts": { + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpatialNback" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jspsych/jspsych-timelines.git", + "directory": "" + }, + "keywords": [ + "jsPsych" + ], + "author": { + "name": "A. Hunter Farhat", + "url": "'use strict';https://github.com/farhat60" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/jspsych/jspsych-timelines/packages/spatial-nback#readme" + }, + "homepage": "/spatial-nback/README.md", + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + }, + "dependencies": { + "@jspsych/plugin-html-button-response": "^2.1.0", + "@jspsych/plugin-html-keyboard-response": "^2.1.0", + "@jspsych-contrib/plugin-spatial-nback": "git+ssh://git@https://github.com/farhat60/jspsych-contrib/packages/plugin-spatial-nback", + "jsPsych": "npm:jspsych@^8.2.1" + } +} diff --git a/packages/spatial-nback/src/index.ts b/packages/spatial-nback/src/index.ts new file mode 100644 index 00000000..58e45cc7 --- /dev/null +++ b/packages/spatial-nback/src/index.ts @@ -0,0 +1,269 @@ +import { initJsPsych, JsPsych } from "jspsych" +import jsPsychPluginSpatialNback from "@jspsych-contrib/plugin-spatial-nback"; +import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; +import jsPsychHtmlButtonResponse from "@jspsych/plugin-html-button-response"; + +const instrictions_template = { + type: jsPsychHtmlButtonResponse, + stimulus: ` +
+

Spatial N-Back Task

+

In this task, you will see a grid with blue squares appearing in different positions.

+

Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.

+

Try to respond as quickly and accurately as possible.

+

Click the button below to begin the task.

+
+ `, + choices: ['Continue'], +}; + +// Generate stimulus sequence for n-back task +function generateNBackSequence(total_trials: number, n_back_level: number, target_percentage: number, rows: number, cols: number) { + const positions: Array<{row: number, col: number}> = []; + const is_target: boolean[] = []; + + // Generate first n trials (cannot be targets) + for (let i = 0; i < n_back_level; i++) { + positions.push({ + row: Math.floor(Math.random() * rows), + col: Math.floor(Math.random() * cols) + }); + is_target.push(false); + } + + // Calculate number of targets to place + const n_targets = Math.round((target_percentage / 100) * (total_trials - n_back_level)); + let targets_placed = 0; + + // Generate remaining trials with targets + for (let i = n_back_level; i < total_trials; i++) { + const can_be_target = targets_placed < n_targets; + const should_be_target = can_be_target && Math.random() < 0.5; + + if (should_be_target) { + // Make this a target trial (same position as n trials back) + positions.push({ + row: positions[i - n_back_level].row, + col: positions[i - n_back_level].col + }); + is_target.push(true); + targets_placed++; + } else { + // Generate non-target position + let new_position: {row: number, col: number}; + do { + new_position = { + row: Math.floor(Math.random() * rows), + col: Math.floor(Math.random() * cols) + }; + } while ( + new_position.row === positions[i - n_back_level].row && + new_position.col === positions[i - n_back_level].col + ); + positions.push(new_position); + is_target.push(false); + } + } + + return { positions, is_target }; +} + +export function createSpatialNBackTimeline({ + rows = 3, + cols = 3, + n_back_level = 1, + total_trials = 20, + target_percentage = 25, + stimulus_duration = 750, + isi_duration = 250, + feedback_duration = 1000, + show_feedback = false, + show_feedback_border = false, + showFeedbackNoResponse = false, + feedbackWaitNoResponse = true, + cell_size = 150, + instructions_trial = "Click the button when the position matches the one from {n} trial(s) ago", + button_text = "MATCH", + stimulus_color = "#2196F3", + correct_color = "#4CAF50", + incorrect_color = "#F44336", + include_instructions = false, + randomize_trials = false, +}: { + rows?: number, + cols?: number, + n_back_level?: number, + total_trials?: number, + target_percentage?: number, + stimulus_duration?: number, + isi_duration?: number, + feedback_duration?: number, + show_feedback?: boolean, + show_feedback_border?: boolean, + showFeedbackNoResponse?: boolean, + feedbackWaitNoResponse?: boolean, + cell_size?: number, + instructions_trial?: string, + button_text?: string, + stimulus_color?: string, + correct_color?: string, + incorrect_color?: string, + include_instructions?: boolean, + randomize_trials?: boolean, +} = {}) { + + // Generate the sequence + const sequence = generateNBackSequence(total_trials, n_back_level, target_percentage, rows, cols); + + // Create individual trial objects + const trials = []; + for (let i = 0; i < total_trials; i++) { + const trial_instructions = instructions_trial + .replace('{n}', n_back_level.toString()) + .replace('{trial}', (i + 1).toString()) + .replace('{total}', total_trials.toString()); + + trials.push({ + type: jsPsychPluginSpatialNback, + rows: rows, + cols: cols, + stimulus_row: sequence.positions[i].row, + stimulus_col: sequence.positions[i].col, + is_target: sequence.is_target[i], + stimulus_duration: stimulus_duration, + isi_duration: isi_duration, + feedback_duration: feedback_duration, + show_feedback: show_feedback, + show_feedback_border: show_feedback_border, + showFeedbackNoResponse: showFeedbackNoResponse, + feedbackWaitNoResponse: feedbackWaitNoResponse, + cell_size: cell_size, + instructions: trial_instructions, + button_text: button_text, + stimulus_color: stimulus_color, + correct_color: correct_color, + incorrect_color: incorrect_color, + data: { + trial_number: i + 1, + n_back_level: n_back_level, + total_trials: total_trials, + task: 'spatial-nback' + } + }); + } + + // Create the main task timeline + const task_timeline = { + timeline: trials, + randomize_order: randomize_trials + }; + + // Return complete timeline with or without instructions + if (include_instructions) { + // Update instructions to show current n-back level + const custom_instructions = { + ...instrictions_template, + stimulus: instrictions_template.stimulus.replace( + '1 trial ago', + `${n_back_level} trial${n_back_level > 1 ? 's' : ''} ago` + ) + }; + + return { + timeline: [custom_instructions, task_timeline] + }; + } else { + return task_timeline; + } +} + +// Create a practice timeline with fewer trials +export function createPracticeTimeline(options: Parameters[0] = {}) { + return createSpatialNBackTimeline({ + ...options, + total_trials: 6, + target_percentage: 33, + show_feedback: true, + show_feedback_border: true, + include_instructions: true + }); +} + +// Create multiple n-back levels timeline +export function createMultiLevelNBackTimeline({ + n_back_levels = [1, 2], + trials_per_level = 20, + randomize_levels = false, + ...sharedOptions +}: { + n_back_levels?: number[], + trials_per_level?: number, + randomize_levels?: boolean, +} & Parameters[0] = {}) { + + const level_timelines = n_back_levels.map(level => { + return createSpatialNBackTimeline({ + ...sharedOptions, + n_back_level: level, + total_trials: trials_per_level, + include_instructions: false + }); + }); + + return { + timeline: level_timelines, + randomize_order: randomize_levels + }; +} + +// Utility functions for common configurations +export const presetConfigurations = { + easy: () => createSpatialNBackTimeline({ + n_back_level: 1, + total_trials: 20, + target_percentage: 30, + show_feedback: true + }), + + medium: () => createSpatialNBackTimeline({ + n_back_level: 2, + total_trials: 30, + target_percentage: 25, + show_feedback: false + }), + + hard: () => createSpatialNBackTimeline({ + n_back_level: 3, + total_trials: 40, + target_percentage: 20, + show_feedback: false, + rows: 4, + cols: 4 + }), + + research: () => createMultiLevelNBackTimeline({ + n_back_levels: [1, 2, 3], + trials_per_level: 50, + target_percentage: 25, + show_feedback: false, + randomize_levels: true + }) +}; + +// Export individual components for custom use +export { instrictions_template, generateNBackSequence }; + +// Export default timeline creator +export default createSpatialNBackTimeline; + +export const timelineUnits = { + createPracticeTimeline, + createSpatialNBackTimeline, + createMultiLevelNBackTimeline, +}; + +export const utils = { + presetConfigurations, + generateNBackSequence, + instrictions_template +} \ No newline at end of file diff --git a/packages/spatial-nback/tsconfig.json b/packages/spatial-nback/tsconfig.json new file mode 100644 index 00000000..33655f70 --- /dev/null +++ b/packages/spatial-nback/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@jspsych/config/tsconfig.contrib.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": [ + "src", + "../cli/src/build.js" + ] +} \ No newline at end of file From ba323852ce6789b100bf5cc870953520dc425180 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Thu, 12 Jun 2025 12:45:48 -0400 Subject: [PATCH 06/37] added some tests --- packages/spatial-nback/src/index.spec.ts | 205 +++++++++++++++++++++++ packages/spatial-nback/src/index.ts | 2 +- 2 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 packages/spatial-nback/src/index.spec.ts diff --git a/packages/spatial-nback/src/index.spec.ts b/packages/spatial-nback/src/index.spec.ts new file mode 100644 index 00000000..c2782c09 --- /dev/null +++ b/packages/spatial-nback/src/index.spec.ts @@ -0,0 +1,205 @@ +import { + createSpatialNBackTimeline, + createPracticeTimeline, + createMultiLevelNBackTimeline, + presetConfigurations, +} from './index'; + +describe('Spatial N-Back Timeline', () => { + describe('createSpatialNBackTimeline', () => { + it('should return a timeline object with correct structure', () => { + const timeline = createSpatialNBackTimeline({ total_trials: 5 }); + + expect(timeline).toHaveProperty('timeline'); + expect(Array.isArray(timeline.timeline)).toBe(true); + expect(timeline.timeline.length).toBe(5); + }); + + it('should use default parameters when none provided', () => { + const timeline = createSpatialNBackTimeline(); + + expect(timeline.timeline.length).toBe(20); // default total_trials + expect(timeline.timeline[0]).toHaveProperty('rows', 3); + expect(timeline.timeline[0]).toHaveProperty('cols', 3); + expect(timeline.timeline[0]).toHaveProperty('n_back_level', 1); + }); + + it('should set randomize_order when randomize_trials is true', () => { + const timeline = createSpatialNBackTimeline({ + total_trials: 5, + randomize_trials: true + }); + + expect(timeline.randomize_order).toBe(true); + }); + + it('should not set randomize_order when randomize_trials is false', () => { + const timeline = createSpatialNBackTimeline({ + total_trials: 5, + randomize_trials: false + }); + + expect(timeline.randomize_order).toBeUndefined(); + }); + + it('should include instructions trial when include_instructions is true', () => { + const timeline = createSpatialNBackTimeline({ + total_trials: 5, + include_instructions: true + }); + + // First trial should be instructions + expect(timeline.timeline[0].stimulus).toContain('Spatial N-Back Task'); + expect(timeline.timeline.length).toBe(6); // 5 trials + 1 instruction + }); + + it('should pass custom parameters to trial objects', () => { + const customParams = { + total_trials: 3, + rows: 4, + cols: 5, + stimulus_color: '#ff0000', + button_text: 'CUSTOM', + n_back_level: 2 + }; + + const timeline = createSpatialNBackTimeline(customParams); + + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].cols).toBe(5); + expect(timeline.timeline[0].stimulus_color).toBe('#ff0000'); + expect(timeline.timeline[0].button_text).toBe('CUSTOM'); + }); + + it('should generate valid target sequences', () => { + const timeline = createSpatialNBackTimeline({ + total_trials: 10, + n_back_level: 2, + target_percentage: 30 + }); + + // Check that early trials are not targets (can't be n-back matches) + expect(timeline.timeline[0].is_target).toBe(false); + expect(timeline.timeline[1].is_target).toBe(false); + + // Check that some trials are targets + const targets = timeline.timeline.filter(trial => trial.is_target); + expect(targets.length).toBeGreaterThan(0); + expect(targets.length).toBeLessThanOrEqual(8); // Max possible with n_back_level=2 + }); + }); + + describe('createPracticeTimeline', () => { + it('should create practice timeline with correct defaults', () => { + const timeline = createPracticeTimeline(); + + expect(timeline.timeline.length).toBe(6); + expect(timeline.timeline[0].show_feedback).toBe(true); + expect(timeline.timeline[0].target_percentage).toBe(33); + }); + + it('should override defaults with custom parameters', () => { + const timeline = createPracticeTimeline({ + rows: 4, + stimulus_color: '#00ff00' + }); + + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].stimulus_color).toBe('#00ff00'); + expect(timeline.timeline[0].show_feedback).toBe(true); // Should still be true + }); + }); + + describe('createMultiLevelNBackTimeline', () => { + it('should create timeline with multiple n-back levels', () => { + const timeline = createMultiLevelNBackTimeline({ + n_back_levels: [1, 2, 3], + trials_per_level: 5 + }); + + expect(timeline.timeline.length).toBe(3); + expect(timeline.timeline[0].timeline.length).toBe(5); + expect(timeline.timeline[1].timeline.length).toBe(5); + expect(timeline.timeline[2].timeline.length).toBe(5); + }); + + it('should set correct n_back_level for each sub-timeline', () => { + const timeline = createMultiLevelNBackTimeline({ + n_back_levels: [1, 2, 3], + trials_per_level: 3 + }); + + expect(timeline.timeline[0].timeline[0].n_back_level).toBe(1); + expect(timeline.timeline[1].timeline[0].n_back_level).toBe(2); + expect(timeline.timeline[2].timeline[0].n_back_level).toBe(3); + }); + + it('should randomize level order when randomize_levels is true', () => { + const timeline = createMultiLevelNBackTimeline({ + n_back_levels: [1, 2], + randomize_levels: true + }); + + expect(timeline.randomize_order).toBe(true); + }); + }); + + describe('presetConfigurations', () => { + it('easy preset should have correct settings', () => { + const timeline = presetConfigurations.easy(); + + expect(timeline.timeline[0].n_back_level).toBe(1); + expect(timeline.timeline[0].show_feedback).toBe(true); + expect(timeline.timeline[0].total_trials).toBe(15); + }); + + it('medium preset should have correct settings', () => { + const timeline = presetConfigurations.medium(); + + expect(timeline.timeline[0].n_back_level).toBe(2); + expect(timeline.timeline[0].show_feedback).toBe(false); + expect(timeline.timeline[0].total_trials).toBe(20); + }); + + it('hard preset should have correct settings', () => { + const timeline = presetConfigurations.hard(); + + expect(timeline.timeline[0].n_back_level).toBe(3); + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].cols).toBe(4); + expect(timeline.timeline[0].total_trials).toBe(25); + }); + + it('research preset should create multi-level timeline', () => { + const timeline = presetConfigurations.research(); + + expect(Array.isArray(timeline.timeline)).toBe(true); + expect(timeline.timeline.length).toBe(3); // 1-back, 2-back, 3-back + }); + }); + + describe('parameter validation', () => { + it('should handle edge case parameters', () => { + const timeline = createSpatialNBackTimeline({ + total_trials: 1, + target_percentage: 0, + n_back_level: 1 + }); + + expect(timeline.timeline.length).toBe(1); + expect(timeline.timeline[0].is_target).toBe(false); // Can't be target with n_back_level=1 + }); + + it('should handle high target percentage correctly', () => { + const timeline = createSpatialNBackTimeline({ + total_trials: 10, + target_percentage: 90, + n_back_level: 1 + }); + + // Should not exceed maximum possible targets + const targets = timeline.timeline.filter(trial => trial.is_target); + expect(targets.length).toBeLessThanOrEqual(9); // Max with n_back_level=1 and 10 trials + }); + }); +}); \ No newline at end of file diff --git a/packages/spatial-nback/src/index.ts b/packages/spatial-nback/src/index.ts index 58e45cc7..cc480e4a 100644 --- a/packages/spatial-nback/src/index.ts +++ b/packages/spatial-nback/src/index.ts @@ -1,5 +1,5 @@ import { initJsPsych, JsPsych } from "jspsych" -import jsPsychPluginSpatialNback from "@jspsych-contrib/plugin-spatial-nback"; +import jsPsychPluginSpatialNback from "@jspsych/plugin-spatial-nback"; import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; import jsPsychHtmlButtonResponse from "@jspsych/plugin-html-button-response"; From 39f182bcaba23e2a5d3717bf7611fcabe2706222 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Thu, 12 Jun 2025 15:13:35 -0400 Subject: [PATCH 07/37] citations file fixed --- packages/spatial-nback/docs/spatial-nback.md | 2 +- packages/spatial-nback/examples/index.html | 2 +- packages/spatial-nback/package.json | 2 +- packages/spatial-nback/src/index.spec.ts | 29 +++++++++----------- packages/spatial-nback/src/index.ts | 20 +++++++------- 5 files changed, 26 insertions(+), 29 deletions(-) diff --git a/packages/spatial-nback/docs/spatial-nback.md b/packages/spatial-nback/docs/spatial-nback.md index 0c1520d7..a0e3d2db 100644 --- a/packages/spatial-nback/docs/spatial-nback.md +++ b/packages/spatial-nback/docs/spatial-nback.md @@ -71,7 +71,7 @@ rt | number | Response time in ms The following functions/utilities are exported for custom use: -### createSpatialNBackTimeline(params) +### createTimeline(params) Creates a spatial n-back timeline with the specified parameters. ### createPracticeTimeline(params) diff --git a/packages/spatial-nback/examples/index.html b/packages/spatial-nback/examples/index.html index 6965da68..9ce28a66 100644 --- a/packages/spatial-nback/examples/index.html +++ b/packages/spatial-nback/examples/index.html @@ -24,7 +24,7 @@ const instructions_trial = jsPsychTimelineSpatialNback.utils.instrictions_template; // Create a custom timeline - const spatial1Back = jsPsychTimelineSpatialNback.createSpatialNBackTimeline({ + const spatial1Back = jsPsychTimelineSpatialNback.createTimeline({ rows: 3, cols: 3, n_back_level: 1, diff --git a/packages/spatial-nback/package.json b/packages/spatial-nback/package.json index 9c101719..ccd4732c 100644 --- a/packages/spatial-nback/package.json +++ b/packages/spatial-nback/package.json @@ -35,7 +35,7 @@ "dependencies": { "@jspsych/plugin-html-button-response": "^2.1.0", "@jspsych/plugin-html-keyboard-response": "^2.1.0", - "@jspsych-contrib/plugin-spatial-nback": "git+ssh://git@https://github.com/farhat60/jspsych-contrib/packages/plugin-spatial-nback", + "@jspsych-contrib/plugin-spatial-nback": "git@https://github.com/farhat60/jspsych-contrib/packages/plugin-spatial-nback", "jsPsych": "npm:jspsych@^8.2.1" } } diff --git a/packages/spatial-nback/src/index.spec.ts b/packages/spatial-nback/src/index.spec.ts index c2782c09..4d1c4fb6 100644 --- a/packages/spatial-nback/src/index.spec.ts +++ b/packages/spatial-nback/src/index.spec.ts @@ -1,14 +1,11 @@ -import { - createSpatialNBackTimeline, - createPracticeTimeline, - createMultiLevelNBackTimeline, - presetConfigurations, -} from './index'; +import { startTimeline } from "@jspsych/test-utils"; +import jsPsychPluginSpatialNback from "@jspsych/plugin-spatial-nback"; +import { JsPsych } from "jspsych"; describe('Spatial N-Back Timeline', () => { - describe('createSpatialNBackTimeline', () => { + describe('createTimeline', () => { it('should return a timeline object with correct structure', () => { - const timeline = createSpatialNBackTimeline({ total_trials: 5 }); + const timeline = createTimeline({ total_trials: 5 }); expect(timeline).toHaveProperty('timeline'); expect(Array.isArray(timeline.timeline)).toBe(true); @@ -16,7 +13,7 @@ describe('Spatial N-Back Timeline', () => { }); it('should use default parameters when none provided', () => { - const timeline = createSpatialNBackTimeline(); + const timeline = createTimeline(); expect(timeline.timeline.length).toBe(20); // default total_trials expect(timeline.timeline[0]).toHaveProperty('rows', 3); @@ -25,7 +22,7 @@ describe('Spatial N-Back Timeline', () => { }); it('should set randomize_order when randomize_trials is true', () => { - const timeline = createSpatialNBackTimeline({ + const timeline = createTimeline({ total_trials: 5, randomize_trials: true }); @@ -34,7 +31,7 @@ describe('Spatial N-Back Timeline', () => { }); it('should not set randomize_order when randomize_trials is false', () => { - const timeline = createSpatialNBackTimeline({ + const timeline = createTimeline({ total_trials: 5, randomize_trials: false }); @@ -43,7 +40,7 @@ describe('Spatial N-Back Timeline', () => { }); it('should include instructions trial when include_instructions is true', () => { - const timeline = createSpatialNBackTimeline({ + const timeline = createTimeline({ total_trials: 5, include_instructions: true }); @@ -63,7 +60,7 @@ describe('Spatial N-Back Timeline', () => { n_back_level: 2 }; - const timeline = createSpatialNBackTimeline(customParams); + const timeline = createTimeline(customParams); expect(timeline.timeline[0].rows).toBe(4); expect(timeline.timeline[0].cols).toBe(5); @@ -72,7 +69,7 @@ describe('Spatial N-Back Timeline', () => { }); it('should generate valid target sequences', () => { - const timeline = createSpatialNBackTimeline({ + const timeline = createTimeline({ total_trials: 10, n_back_level: 2, target_percentage: 30 @@ -180,7 +177,7 @@ describe('Spatial N-Back Timeline', () => { describe('parameter validation', () => { it('should handle edge case parameters', () => { - const timeline = createSpatialNBackTimeline({ + const timeline = createTimeline({ total_trials: 1, target_percentage: 0, n_back_level: 1 @@ -191,7 +188,7 @@ describe('Spatial N-Back Timeline', () => { }); it('should handle high target percentage correctly', () => { - const timeline = createSpatialNBackTimeline({ + const timeline = createTimeline({ total_trials: 10, target_percentage: 90, n_back_level: 1 diff --git a/packages/spatial-nback/src/index.ts b/packages/spatial-nback/src/index.ts index cc480e4a..c0224c6c 100644 --- a/packages/spatial-nback/src/index.ts +++ b/packages/spatial-nback/src/index.ts @@ -68,7 +68,7 @@ function generateNBackSequence(total_trials: number, n_back_level: number, targe return { positions, is_target }; } -export function createSpatialNBackTimeline({ +export function createTimeline({ rows = 3, cols = 3, n_back_level = 1, @@ -178,8 +178,8 @@ export function createSpatialNBackTimeline({ } // Create a practice timeline with fewer trials -export function createPracticeTimeline(options: Parameters[0] = {}) { - return createSpatialNBackTimeline({ +export function createPracticeTimeline(options: Parameters[0] = {}) { + return createTimeline({ ...options, total_trials: 6, target_percentage: 33, @@ -199,10 +199,10 @@ export function createMultiLevelNBackTimeline({ n_back_levels?: number[], trials_per_level?: number, randomize_levels?: boolean, -} & Parameters[0] = {}) { +} & Parameters[0] = {}) { const level_timelines = n_back_levels.map(level => { - return createSpatialNBackTimeline({ + return createTimeline({ ...sharedOptions, n_back_level: level, total_trials: trials_per_level, @@ -218,21 +218,21 @@ export function createMultiLevelNBackTimeline({ // Utility functions for common configurations export const presetConfigurations = { - easy: () => createSpatialNBackTimeline({ + easy: () => createTimeline({ n_back_level: 1, total_trials: 20, target_percentage: 30, show_feedback: true }), - medium: () => createSpatialNBackTimeline({ + medium: () => createTimeline({ n_back_level: 2, total_trials: 30, target_percentage: 25, show_feedback: false }), - hard: () => createSpatialNBackTimeline({ + hard: () => createTimeline({ n_back_level: 3, total_trials: 40, target_percentage: 20, @@ -254,11 +254,11 @@ export const presetConfigurations = { export { instrictions_template, generateNBackSequence }; // Export default timeline creator -export default createSpatialNBackTimeline; +export default createTimeline; export const timelineUnits = { createPracticeTimeline, - createSpatialNBackTimeline, + createTimeline, createMultiLevelNBackTimeline, }; From 8a163c7d59dce1640662af116abc05a093f89336 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 16 Jun 2025 15:47:18 -0400 Subject: [PATCH 08/37] completed test suite, all tests pass. --- packages/pattern-comparison-task/README.md | 50 ++ .../docs/pattern-comparison-task.md} | 8 +- .../examples/index copy.html | 35 + .../examples/index.html | 35 + .../jest.config.js | 0 packages/pattern-comparison-task/package.json | 35 + .../pattern-comparison-task/src/index copy.ts | 299 +++++++++ packages/pattern-comparison-task/src/index.ts | 299 +++++++++ .../tsconfig.json | 0 packages/pattern-comparison-test/README.md | 50 ++ .../docs/pattern-comparison-test.md | 51 ++ .../examples/index.html | 578 ++++++++++++++++ .../jest.config.js | 0 packages/pattern-comparison-test/package.json | 35 + .../pattern-comparison-test/src/index copy.ts | 9 + packages/pattern-comparison-test/src/index.ts | 632 ++++++++++++++++++ .../pattern-comparison-test/tsconfig.json | 10 + packages/spatial-nback-task/.gitignore | 47 -- packages/spatial-nback-task/README.md | 36 - .../spatial-nback-task/examples/index.html | 62 -- packages/spatial-nback-task/package.json | 40 -- packages/spatial-nback-task/src/index.ts | 269 -------- packages/spatial-nback/.gitignore | 47 -- packages/spatial-nback/docs/spatial-nback.md | 14 +- packages/spatial-nback/examples/index.html | 15 +- packages/spatial-nback/jest.config.cjs | 1 + .../package-lock.json | 0 packages/spatial-nback/src/index.spec copy.ts | 188 ++++++ packages/spatial-nback/src/index.spec.ts | 335 +++++----- packages/spatial-nback/src/index.ts | 88 +-- 30 files changed, 2535 insertions(+), 733 deletions(-) create mode 100644 packages/pattern-comparison-task/README.md rename packages/{spatial-nback-task/docs/spatial-nback-task.md => pattern-comparison-task/docs/pattern-comparison-task.md} (72%) create mode 100644 packages/pattern-comparison-task/examples/index copy.html create mode 100644 packages/pattern-comparison-task/examples/index.html rename packages/{spatial-nback-task => pattern-comparison-task}/jest.config.js (100%) create mode 100644 packages/pattern-comparison-task/package.json create mode 100644 packages/pattern-comparison-task/src/index copy.ts create mode 100644 packages/pattern-comparison-task/src/index.ts rename packages/{spatial-nback-task => pattern-comparison-task}/tsconfig.json (100%) create mode 100644 packages/pattern-comparison-test/README.md create mode 100644 packages/pattern-comparison-test/docs/pattern-comparison-test.md create mode 100644 packages/pattern-comparison-test/examples/index.html rename packages/{spatial-nback => pattern-comparison-test}/jest.config.js (100%) create mode 100644 packages/pattern-comparison-test/package.json create mode 100644 packages/pattern-comparison-test/src/index copy.ts create mode 100644 packages/pattern-comparison-test/src/index.ts create mode 100644 packages/pattern-comparison-test/tsconfig.json delete mode 100644 packages/spatial-nback-task/.gitignore delete mode 100644 packages/spatial-nback-task/README.md delete mode 100644 packages/spatial-nback-task/examples/index.html delete mode 100644 packages/spatial-nback-task/package.json delete mode 100644 packages/spatial-nback-task/src/index.ts delete mode 100644 packages/spatial-nback/.gitignore create mode 100644 packages/spatial-nback/jest.config.cjs rename packages/{spatial-nback-task => spatial-nback}/package-lock.json (100%) create mode 100644 packages/spatial-nback/src/index.spec copy.ts diff --git a/packages/pattern-comparison-task/README.md b/packages/pattern-comparison-task/README.md new file mode 100644 index 00000000..6513db10 --- /dev/null +++ b/packages/pattern-comparison-task/README.md @@ -0,0 +1,50 @@ +# pattern-comparison-task + +## Overview + +An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same. + +## Loading + +### In browser + +```html + + + + + + + + + \ No newline at end of file diff --git a/packages/pattern-comparison-task/examples/index.html b/packages/pattern-comparison-task/examples/index.html new file mode 100644 index 00000000..725529f9 --- /dev/null +++ b/packages/pattern-comparison-task/examples/index.html @@ -0,0 +1,35 @@ + + + + + + Pattern Comparison Task + + + + + + + + + \ No newline at end of file diff --git a/packages/spatial-nback-task/jest.config.js b/packages/pattern-comparison-task/jest.config.js similarity index 100% rename from packages/spatial-nback-task/jest.config.js rename to packages/pattern-comparison-task/jest.config.js diff --git a/packages/pattern-comparison-task/package.json b/packages/pattern-comparison-task/package.json new file mode 100644 index 00000000..1a8c0a83 --- /dev/null +++ b/packages/pattern-comparison-task/package.json @@ -0,0 +1,35 @@ +{ + "name": "@jspsych-timelines/pattern-comparison-task", + "version": "0.0.1", + "description": "An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same.", + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.ts", + "unpkg": "dist/index.browser.min.js", + "scripts": { + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelinePatternComparisonTask" + }, + "repository": { + "type": "git", + "url": "https://github.com/farhat60/jspsych-timelines/", + "directory": "packages" + }, + "keywords": [ + "jsPsych" + ], + "author": { + "name": "Abdullah Hunter Farhat", + "url": "github.com/farhat60" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/farhat60/jspsych-timelines//issues" + }, + "homepage": "https://github.com/jspsych/jspsych-timelines/packages/pattern-comparison-task/README.md", + "devDependencies": { + "jspsych": "^8.0.0", + "@jspsych/config": "^3.2.2", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + } \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/index copy.ts b/packages/pattern-comparison-task/src/index copy.ts new file mode 100644 index 00000000..72ec6605 --- /dev/null +++ b/packages/pattern-comparison-task/src/index copy.ts @@ -0,0 +1,299 @@ +import { JsPsych, TrialType } from "jspsych" +import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" + +interface PatternComparisonConfig { + /** Array of SVG strings or URLs to use as patterns */ + patterns?: string[] + /** Number of trials to generate */ + numTrials?: number + /** Instructions text to display above each trial */ + instructions?: string + /** Enable text-to-speech for instructions */ + enableTTS?: boolean + /** Text for the "same" button */ + sameButtonText?: string + /** Text for the "different" button */ + differentButtonText?: string + /** Maximum time allowed per trial (in ms) */ + trialTimeout?: number + /** Inter-trial interval (in ms) */ + interTrialInterval?: number + /** Number of colored patterns to generate */ + numPatterns?: number +} + +// Base SVG shapes without colors +const svgShapes = [ + // Lightbulb + ``, + + // Camera + ``, + + // Key + ``, + + // Book + ``, + + // Anchor + ``, + + // Envelope (Mail) + ``, + + // Gift Box + ``, + + // Flag + ``, +]; + +// Color palette for random selection +const colors = [ + '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', + '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9', + '#F8C471', '#82E0AA', '#F1948A', '#85C1E9', '#D5A6BD', + '#A569BD', '#5DADE2', '#58D68D', '#F4D03F', '#EB984E' +]; + +function generatePatterns(numPatterns: number = 20): string[] { + const patterns = []; + + for (let i = 0; i < numPatterns; i++) { + // Select random shape + const shapeIndex = Math.floor(Math.random() * svgShapes.length); + const baseShape = svgShapes[shapeIndex]; + + // Select random colors + const fillColor = colors[Math.floor(Math.random() * colors.length)]; + const strokeColor = colors[Math.floor(Math.random() * colors.length)]; + + // Replace placeholders with actual colors + const coloredPattern = baseShape + .replace(/FILL_COLOR/g, fillColor) + .replace(/STROKE_COLOR/g, strokeColor); + + patterns.push(coloredPattern); + } + + return patterns; +} + +function generateTrials(config: PatternComparisonConfig) { + // Use patterns from config or generate colored patterns + const patterns = config.patterns || generatePatterns(config.numPatterns || 20); + const numTrials = config.numTrials || 20; + const trials = []; + + for (let i = 0; i < numTrials; i++) { + // Randomly decide if patterns should be same or different + const isSame = Math.random() < 0.5; + + // Select first pattern randomly + const pattern1Index = Math.floor(Math.random() * patterns.length); + const pattern1 = patterns[pattern1Index]; + + // Select second pattern based on isSame + let pattern2; + if (isSame) { + pattern2 = pattern1; + } else { + let pattern2Index; + do { + pattern2Index = Math.floor(Math.random() * patterns.length); + } while (pattern2Index === pattern1Index); + pattern2 = patterns[pattern2Index]; + } + + trials.push({ + pattern1, + pattern2, + correctAnswer: isSame ? 0 : 1 // 0 for same, 1 for different + }); + } + + return trials; +} + +export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig = {}) { + const { + instructions = "Are these two patterns the same?", + enableTTS = false, + sameButtonText = "Same", + differentButtonText = "Different", + trialTimeout = 10000, + interTrialInterval = 500 + } = config + + const trials = generateTrials(config) + const timeline = [] + + // Instructions screen + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

Pattern Comparison Task

+

You will see two patterns side by side. Your task is to decide whether they are the same or different.

+

Respond as quickly and accurately as possible.

+

Click the button below to start.

+
+ `, + choices: ['Start'], + margin_horizontal: '10px', + margin_vertical: '10px' + }) + + // Create trial timeline + trials.forEach((trial, index) => { + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` + +
+
${instructions}
+
+
${trial.pattern1}
+
${trial.pattern2}
+
+
+ `, + choices: [sameButtonText, differentButtonText], + margin_horizontal: '20px', + margin_vertical: '15px', + button_html: function(choice, choice_index) { + return ``; + }, + trial_duration: trialTimeout, + data: { + task: 'pattern-comparison', + trial_number: index + 1, + correct_answer: trial.correctAnswer, + pattern1: trial.pattern1, + pattern2: trial.pattern2, + is_same: trial.correctAnswer === 0 + }, + on_finish: function(data: any) { + data.correct = data.response === data.correct_answer + data.reaction_time = data.rt + }, + on_start: function() { + if (enableTTS && 'speechSynthesis' in window) { + const utterance = new SpeechSynthesisUtterance(instructions) + speechSynthesis.speak(utterance) + } + } + }) + + // Inter-trial interval + if (index < trials.length - 1) { + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: '
+
', + choices: [], + trial_duration: interTrialInterval + }) + } + }) + + // End screen + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

Task Complete!

+

Thank you for participating in the pattern comparison task.

+
+ `, + choices: ['Continue'] + }) + + return timeline +} + +export const timelineUnits = { + instructions: "Instructions for the pattern comparison task", + trial: "Single pattern comparison trial", + interTrialInterval: "Fixation cross between trials", + endScreen: "Task completion screen" +} + +export const utils = { + generateTrials, + generatePatterns, + svgShapes, // Export the base shapes + colors, // Export the color palette + + /** Calculate accuracy and reaction time statistics */ + calculatePerformance: function(data: any[]) { + const trialData = data.filter(d => d.task === 'pattern-comparison') + const correct = trialData.filter(d => d.correct).length + const total = trialData.length + const accuracy = total > 0 ? (correct / total) * 100 : 0 + + const validRTs = trialData.filter(d => d.correct && d.rt !== null).map(d => d.rt) + const meanRT = validRTs.length > 0 ? validRTs.reduce((a, b) => a + b, 0) / validRTs.length : null + + return { + accuracy, + meanReactionTime: meanRT, + totalTrials: total, + correctTrials: correct + } + } +} + +// Default export for convenience +export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/index.ts b/packages/pattern-comparison-task/src/index.ts new file mode 100644 index 00000000..72ec6605 --- /dev/null +++ b/packages/pattern-comparison-task/src/index.ts @@ -0,0 +1,299 @@ +import { JsPsych, TrialType } from "jspsych" +import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" + +interface PatternComparisonConfig { + /** Array of SVG strings or URLs to use as patterns */ + patterns?: string[] + /** Number of trials to generate */ + numTrials?: number + /** Instructions text to display above each trial */ + instructions?: string + /** Enable text-to-speech for instructions */ + enableTTS?: boolean + /** Text for the "same" button */ + sameButtonText?: string + /** Text for the "different" button */ + differentButtonText?: string + /** Maximum time allowed per trial (in ms) */ + trialTimeout?: number + /** Inter-trial interval (in ms) */ + interTrialInterval?: number + /** Number of colored patterns to generate */ + numPatterns?: number +} + +// Base SVG shapes without colors +const svgShapes = [ + // Lightbulb + ``, + + // Camera + ``, + + // Key + ``, + + // Book + ``, + + // Anchor + ``, + + // Envelope (Mail) + ``, + + // Gift Box + ``, + + // Flag + ``, +]; + +// Color palette for random selection +const colors = [ + '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', + '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9', + '#F8C471', '#82E0AA', '#F1948A', '#85C1E9', '#D5A6BD', + '#A569BD', '#5DADE2', '#58D68D', '#F4D03F', '#EB984E' +]; + +function generatePatterns(numPatterns: number = 20): string[] { + const patterns = []; + + for (let i = 0; i < numPatterns; i++) { + // Select random shape + const shapeIndex = Math.floor(Math.random() * svgShapes.length); + const baseShape = svgShapes[shapeIndex]; + + // Select random colors + const fillColor = colors[Math.floor(Math.random() * colors.length)]; + const strokeColor = colors[Math.floor(Math.random() * colors.length)]; + + // Replace placeholders with actual colors + const coloredPattern = baseShape + .replace(/FILL_COLOR/g, fillColor) + .replace(/STROKE_COLOR/g, strokeColor); + + patterns.push(coloredPattern); + } + + return patterns; +} + +function generateTrials(config: PatternComparisonConfig) { + // Use patterns from config or generate colored patterns + const patterns = config.patterns || generatePatterns(config.numPatterns || 20); + const numTrials = config.numTrials || 20; + const trials = []; + + for (let i = 0; i < numTrials; i++) { + // Randomly decide if patterns should be same or different + const isSame = Math.random() < 0.5; + + // Select first pattern randomly + const pattern1Index = Math.floor(Math.random() * patterns.length); + const pattern1 = patterns[pattern1Index]; + + // Select second pattern based on isSame + let pattern2; + if (isSame) { + pattern2 = pattern1; + } else { + let pattern2Index; + do { + pattern2Index = Math.floor(Math.random() * patterns.length); + } while (pattern2Index === pattern1Index); + pattern2 = patterns[pattern2Index]; + } + + trials.push({ + pattern1, + pattern2, + correctAnswer: isSame ? 0 : 1 // 0 for same, 1 for different + }); + } + + return trials; +} + +export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig = {}) { + const { + instructions = "Are these two patterns the same?", + enableTTS = false, + sameButtonText = "Same", + differentButtonText = "Different", + trialTimeout = 10000, + interTrialInterval = 500 + } = config + + const trials = generateTrials(config) + const timeline = [] + + // Instructions screen + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

Pattern Comparison Task

+

You will see two patterns side by side. Your task is to decide whether they are the same or different.

+

Respond as quickly and accurately as possible.

+

Click the button below to start.

+
+ `, + choices: ['Start'], + margin_horizontal: '10px', + margin_vertical: '10px' + }) + + // Create trial timeline + trials.forEach((trial, index) => { + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` + +
+
${instructions}
+
+
${trial.pattern1}
+
${trial.pattern2}
+
+
+ `, + choices: [sameButtonText, differentButtonText], + margin_horizontal: '20px', + margin_vertical: '15px', + button_html: function(choice, choice_index) { + return ``; + }, + trial_duration: trialTimeout, + data: { + task: 'pattern-comparison', + trial_number: index + 1, + correct_answer: trial.correctAnswer, + pattern1: trial.pattern1, + pattern2: trial.pattern2, + is_same: trial.correctAnswer === 0 + }, + on_finish: function(data: any) { + data.correct = data.response === data.correct_answer + data.reaction_time = data.rt + }, + on_start: function() { + if (enableTTS && 'speechSynthesis' in window) { + const utterance = new SpeechSynthesisUtterance(instructions) + speechSynthesis.speak(utterance) + } + } + }) + + // Inter-trial interval + if (index < trials.length - 1) { + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: '
+
', + choices: [], + trial_duration: interTrialInterval + }) + } + }) + + // End screen + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

Task Complete!

+

Thank you for participating in the pattern comparison task.

+
+ `, + choices: ['Continue'] + }) + + return timeline +} + +export const timelineUnits = { + instructions: "Instructions for the pattern comparison task", + trial: "Single pattern comparison trial", + interTrialInterval: "Fixation cross between trials", + endScreen: "Task completion screen" +} + +export const utils = { + generateTrials, + generatePatterns, + svgShapes, // Export the base shapes + colors, // Export the color palette + + /** Calculate accuracy and reaction time statistics */ + calculatePerformance: function(data: any[]) { + const trialData = data.filter(d => d.task === 'pattern-comparison') + const correct = trialData.filter(d => d.correct).length + const total = trialData.length + const accuracy = total > 0 ? (correct / total) * 100 : 0 + + const validRTs = trialData.filter(d => d.correct && d.rt !== null).map(d => d.rt) + const meanRT = validRTs.length > 0 ? validRTs.reduce((a, b) => a + b, 0) / validRTs.length : null + + return { + accuracy, + meanReactionTime: meanRT, + totalTrials: total, + correctTrials: correct + } + } +} + +// Default export for convenience +export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/spatial-nback-task/tsconfig.json b/packages/pattern-comparison-task/tsconfig.json similarity index 100% rename from packages/spatial-nback-task/tsconfig.json rename to packages/pattern-comparison-task/tsconfig.json diff --git a/packages/pattern-comparison-test/README.md b/packages/pattern-comparison-test/README.md new file mode 100644 index 00000000..69a12f1f --- /dev/null +++ b/packages/pattern-comparison-test/README.md @@ -0,0 +1,50 @@ +# pattern-comparison-test + +## Overview + +An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same. + +## Loading + +### In browser + +```html + + + + + + + + + + + \ No newline at end of file diff --git a/packages/spatial-nback/jest.config.js b/packages/pattern-comparison-test/jest.config.js similarity index 100% rename from packages/spatial-nback/jest.config.js rename to packages/pattern-comparison-test/jest.config.js diff --git a/packages/pattern-comparison-test/package.json b/packages/pattern-comparison-test/package.json new file mode 100644 index 00000000..b868a28f --- /dev/null +++ b/packages/pattern-comparison-test/package.json @@ -0,0 +1,35 @@ +{ + "name": "@jspsych-timelines/pattern-comparison-test", + "version": "0.0.1", + "description": "An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same.", + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.ts", + "unpkg": "dist/index.browser.min.js", + "scripts": { + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelinePatternComparisonTest" + }, + "repository": { + "type": "git", + "url": "https://github.com/farhat60/jspsych-timelines/", + "directory": "packages" + }, + "keywords": [ + "jsPsych" + ], + "author": { + "name": "A. Hunter Farhat", + "url": "github.com/farhat60" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/farhat60/jspsych-timelines//issues" + }, + "homepage": "https://github.com/jspsych/jspsych-timelines/packages/pattern-comparison-test/README.md", + "devDependencies": { + "jspsych": "^8.0.0", + "@jspsych/config": "^3.2.2", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + } \ No newline at end of file diff --git a/packages/pattern-comparison-test/src/index copy.ts b/packages/pattern-comparison-test/src/index copy.ts new file mode 100644 index 00000000..8138d9f9 --- /dev/null +++ b/packages/pattern-comparison-test/src/index copy.ts @@ -0,0 +1,9 @@ +import { JsPsych } from "jspsych" + +export function createTimeline(jsPsych:JsPsych, {}){ + +} + +export const timelineUnits = {} + +export const utils = {} \ No newline at end of file diff --git a/packages/pattern-comparison-test/src/index.ts b/packages/pattern-comparison-test/src/index.ts new file mode 100644 index 00000000..a9f27135 --- /dev/null +++ b/packages/pattern-comparison-test/src/index.ts @@ -0,0 +1,632 @@ +import { JsPsych } from "jspsych" + +interface PatternComparisonConfig { + numTrials?: number; + stimulusDuration?: number; + fixationDuration?: number; + numPracticeTrials?: number; + showFeedback?: boolean; + patterns?: Array<{ left: string; right: string; correct_response: string }>; + showPromptText?: boolean; + promptText?: string; + enableAudio?: boolean; + useMobileButtons?: boolean; + sameButtonText?: string; + differentButtonText?: string; + sameButtonKey?: string; + differentButtonKey?: string; +} + +// SVG patterns from SVG Repo styled with CSS +const svgShapes = [ + // Star + ` + + `, + // Heart + ` + + `, + // Diamond + ` + + `, + // Circle with pattern + ` + + + + + + + `, + // Flower + ` + + `, + // Triangle + ` + + `, + // Hexagon + ` + + `, + // Cross + ` + + ` +]; + +const colors = [ + '#FF6B6B', // Red + '#4ECDC4', // Teal + '#45B7D1', // Blue + '#96CEB4', // Green + '#FFEAA7', // Yellow + '#DDA0DD', // Plum + '#FFB347', // Orange + '#87CEEB', // Sky Blue + '#F0A3FF', // Bright Pink + '#0075DC' // Bright Blue +]; + +// Text-to-speech utility +function speakText(text: string, enableAudio: boolean = false) { + if (!enableAudio || !('speechSynthesis' in window)) return; + + // Cancel any ongoing speech + speechSynthesis.cancel(); + + const utterance = new SpeechSynthesisUtterance(text); + utterance.rate = 0.8; + utterance.pitch = 1; + utterance.volume = 0.8; + speechSynthesis.speak(utterance); +} + +function generateSVGPattern(shapeIndex?: number, colorIndex?: number) { + const shape = shapeIndex !== undefined ? svgShapes[shapeIndex] : svgShapes[Math.floor(Math.random() * svgShapes.length)]; + const color = colorIndex !== undefined ? colors[colorIndex] : colors[Math.floor(Math.random() * colors.length)]; + return `
${shape}
`; +} + +function generateDefaultPatterns() { + const patterns = []; + + // Generate same patterns (same shape and color) + for (let i = 0; i < 45; i++) { + const shapeIndex = Math.floor(Math.random() * svgShapes.length); + const colorIndex = Math.floor(Math.random() * colors.length); + const pattern = generateSVGPattern(shapeIndex, colorIndex); + + patterns.push({ + left: pattern, + right: pattern, + correct_response: 's' + }); + } + + // Generate different patterns + for (let i = 0; i < 45; i++) { + const leftShapeIndex = Math.floor(Math.random() * svgShapes.length); + const leftColorIndex = Math.floor(Math.random() * colors.length); + + let rightShapeIndex, rightColorIndex; + + // Randomly decide if we change shape, color, or both + const changeType = Math.random(); + if (changeType < 0.33) { + // Change only shape + rightShapeIndex = (leftShapeIndex + 1 + Math.floor(Math.random() * (svgShapes.length - 1))) % svgShapes.length; + rightColorIndex = leftColorIndex; + } else if (changeType < 0.66) { + // Change only color + rightShapeIndex = leftShapeIndex; + rightColorIndex = (leftColorIndex + 1 + Math.floor(Math.random() * (colors.length - 1))) % colors.length; + } else { + // Change both shape and color + rightShapeIndex = (leftShapeIndex + 1 + Math.floor(Math.random() * (svgShapes.length - 1))) % svgShapes.length; + rightColorIndex = (leftColorIndex + 1 + Math.floor(Math.random() * (colors.length - 1))) % colors.length; + } + + patterns.push({ + left: generateSVGPattern(leftShapeIndex, leftColorIndex), + right: generateSVGPattern(rightShapeIndex, rightColorIndex), + correct_response: 'd' + }); + } + + return patterns; +} + +export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig = {}) { + const { + numTrials = 90, + stimulusDuration = 30000, + fixationDuration = 500, + numPracticeTrials = 6, + showFeedback = true, + patterns = generateDefaultPatterns(), + showPromptText = true, + promptText = "Are these the same?", + enableAudio = false, + useMobileButtons = true, + sameButtonText = "Yes", + differentButtonText = "No", + sameButtonKey = 's', + differentButtonKey = 'd' + } = config; + + const timeline = []; + + // Common CSS for mobile responsiveness + const responsiveCSS = ` + + `; + + // Instructions + const instructionsText = ` + Pattern Comparison Test. In this test, you will see pairs of patterns displayed side by side. + Your task is to determine whether the two patterns are exactly the same or different. + Pay attention to both the shape and the color of the patterns. + `; + + const instructions = { + type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', + stimulus: ` + ${responsiveCSS} +
+

Pattern Comparison Test

+

In this test, you will see pairs of patterns displayed side by side.

+

Your task is to determine whether the two patterns are exactly the same or different.

+

Pay attention to both the shape and the color of the patterns.

+
+
+
+

Press '${sameButtonKey.toUpperCase()}' if the patterns are the SAME (identical shape AND color)

+

Press '${differentButtonKey.toUpperCase()}' if the patterns are DIFFERENT (different shape OR color)

+
+
+

Press "${sameButtonText}" if the patterns are the SAME (identical shape AND color)

+

Press "${differentButtonText}" if the patterns are DIFFERENT (different shape OR color)

+
+
+
+

Please respond as quickly and accurately as possible.

+

${useMobileButtons ? 'Tap "Continue" to start the practice trials' : 'Press any key to start the practice trials'}

+
+ `, + ...(useMobileButtons ? { + choices: ['Continue'], + button_html: '' + } : { + choices: "ALL_KEYS" + }), + data: { task: 'pattern-comparison', phase: 'instructions' }, + on_start: function() { + if (enableAudio) { + setTimeout(() => speakText(instructionsText, enableAudio), 100); + } + } + }; + + // Practice instructions + const practiceInstructionsText = ` + Practice Trials. Let's start with a few practice trials to get familiar with the task. + You will receive feedback after each practice trial. + `; + + const practiceInstructions = { + type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', + stimulus: ` + ${responsiveCSS} +
+

Practice Trials

+

Let's start with a few practice trials to get familiar with the task.

+

You will receive feedback after each practice trial.

+

+ Remember: Press '${sameButtonKey.toUpperCase()}' for SAME, '${differentButtonKey.toUpperCase()}' for DIFFERENT + Remember: Press "${sameButtonText}" for SAME, "${differentButtonText}" for DIFFERENT +

+

${useMobileButtons ? 'Tap "Begin" to start' : 'Press any key to begin'}

+
+ `, + ...(useMobileButtons ? { + choices: ['Begin'], + button_html: '' + } : { + choices: "ALL_KEYS" + }), + data: { task: 'pattern-comparison', phase: 'practice-instructions' }, + on_start: function() { + if (enableAudio) { + setTimeout(() => speakText(practiceInstructionsText, enableAudio), 100); + } + } + }; + + // Practice trial + const practiceTrial = { + type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', + stimulus: jsPsych.timelineVariable('stimulus'), + ...(useMobileButtons ? { + choices: [sameButtonText, differentButtonText], + button_html: (choice: string, choice_index: number) => { + const buttonClass = choice_index === 0 ? 'same-button' : 'different-button'; + return ``; + } + } : { + choices: [sameButtonKey, differentButtonKey] + }), + data: jsPsych.timelineVariable('data'), + on_start: function() { + if (enableAudio && showPromptText) { + setTimeout(() => speakText(promptText, enableAudio), 100); + } + }, + on_finish: function(data: any) { + if (useMobileButtons) { + data.response = data.response === 0 ? sameButtonKey : differentButtonKey; + } + data.correct = data.response === data.correct_response; + } + }; + + // Practice feedback + const practiceFeedback = { + type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', + stimulus: function() { + const lastTrial = jsPsych.data.getLastTrialData().values()[0]; + if (lastTrial.correct) { + return ` + ${responsiveCSS} +
+

✓ Correct!

+

${useMobileButtons ? 'Tap "Continue" to proceed' : 'Press any key to continue'}

+
+ `; + } else { + const correctKey = lastTrial.correct_response === sameButtonKey ? sameButtonText : differentButtonText; + return ` + ${responsiveCSS} +
+

✗ Incorrect

+

The correct answer was: ${correctKey}

+

${useMobileButtons ? 'Tap "Continue" to proceed' : 'Press any key to continue'}

+
+ `; + } + }, + ...(useMobileButtons ? { + choices: ['Continue'], + button_html: '' + } : { + choices: "ALL_KEYS" + }), + trial_duration: useMobileButtons ? null : 2500, + data: { task: 'pattern-comparison', phase: 'practice-feedback' } + }; + + // Practice block + const practiceBlock = { + timeline: showFeedback ? [practiceTrial, practiceFeedback] : [practiceTrial], + timeline_variables: patterns.slice(0, numPracticeTrials).map(pattern => ({ + stimulus: createPatternStimulus(pattern, showPromptText, promptText), + data: { + task: 'pattern-comparison', + phase: 'practice', + correct_response: pattern.correct_response + } + })) + }; + + // Main test instructions + const testInstructionsText = ` + Main Test. Great! Now you'll begin the main test. + There will be no feedback, and you should work as quickly and accurately as possible. + `; + + const testInstructions = { + type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', + stimulus: ` + ${responsiveCSS} +
+

Main Test

+

Great! Now you'll begin the main test.

+

There will be no feedback, and you should work as quickly and accurately as possible.

+

+ '${sameButtonKey.toUpperCase()}' for SAME (identical shape and color)
'${differentButtonKey.toUpperCase()}' for DIFFERENT (different shape or color)
+ "${sameButtonText}" for SAME (identical shape and color)
"${differentButtonText}" for DIFFERENT (different shape or color)
+

+

${useMobileButtons ? 'Tap "Begin Test" to start' : 'Press any key to begin the test'}

+
+ `, + ...(useMobileButtons ? { + choices: ['Begin Test'], + button_html: '' + } : { + choices: "ALL_KEYS" + }), + data: { task: 'pattern-comparison', phase: 'test-instructions' }, + on_start: function() { + if (enableAudio) { + setTimeout(() => speakText(testInstructionsText, enableAudio), 100); + } + } + }; + + // Fixation cross + const fixation = { + type: 'html-keyboard-response', + stimulus: `${responsiveCSS}
+
`, + choices: "NO_KEYS", + trial_duration: fixationDuration, + data: { task: 'pattern-comparison', phase: 'fixation' } + }; + + // Main test trial + const testTrial = { + type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', + stimulus: jsPsych.timelineVariable('stimulus'), + ...(useMobileButtons ? { + choices: [sameButtonText, differentButtonText], + button_html: (choice: string, choice_index: number) => { + const buttonClass = choice_index === 0 ? 'same-button' : 'different-button'; + return ``; + } + } : { + choices: [sameButtonKey, differentButtonKey] + }), + data: jsPsych.timelineVariable('data'), + on_start: function() { + if (enableAudio && showPromptText) { + setTimeout(() => speakText(promptText, enableAudio), 100); + } + }, + on_finish: function(data: any) { + if (useMobileButtons) { + data.response = data.response === 0 ? sameButtonKey : differentButtonKey; + } + data.correct = data.response === data.correct_response; + } + }; + + // Test block + const testBlock = { + timeline: fixationDuration > 0 ? [fixation, testTrial] : [testTrial], + timeline_variables: patterns.slice(numPracticeTrials, numPracticeTrials + numTrials).map(pattern => ({ + stimulus: createPatternStimulus(pattern, showPromptText, promptText), + data: { + task: 'pattern-comparison', + phase: 'test', + correct_response: pattern.correct_response + } + })), + randomize_order: true + }; + + // Build timeline + timeline.push(instructions); + + if (numPracticeTrials > 0) { + timeline.push(practiceInstructions, practiceBlock); + } + + timeline.push(testInstructions, testBlock); + + return timeline; +} + +function createPatternStimulus(pattern: { left: string; right: string }, showPromptText: boolean = true, promptText: string = "Are these the same?") { + return ` +
+ ${showPromptText ? `
${promptText}
` : ''} +
+
+
Pattern 1
+
+ ${pattern.left} +
+
+
+
Pattern 2
+
+ ${pattern.right} +
+
+
+
+
+
+ `; +} + +export const timelineUnits = { + instructions: 'instructions', + practiceInstructions: 'practice-instructions', + practice: 'practice', + testInstructions: 'test-instructions', + main: 'main' +} + +export const utils = { + generateSVGPattern, + generateDefaultPatterns, + createPatternStimulus, + speakText, + calculateAccuracy: (data: any[]) => { + const testTrials = data.filter(trial => trial.phase === 'test' || trial.phase === 'practice'); + const correct = testTrials.filter(trial => trial.correct).length; + return testTrials.length > 0 ? (correct / testTrials.length) * 100 : 0; + }, + calculateAverageRT: (data: any[]) => { + const testTrials = data.filter(trial => (trial.phase === 'test' || trial.phase === 'practice') && trial.correct); + if (testTrials.length === 0) return 0; + const rts = testTrials.map(trial => trial.rt); + return rts.reduce((sum, rt) => sum + rt, 0) / rts.length; + }, + getTrialsByPhase: (data: any[], phase: string) => { + return data.filter(trial => trial.phase === phase); + } +} + +// Export constants for external use +export const constants = { + svgShapes, + colors, + defaultConfig: { + numTrials: 90, + stimulusDuration: 30000, + fixationDuration: 500, + numPracticeTrials: 6, + showFeedback: true, + showPromptText: true, + promptText: "Are these the same?", + enableAudio: false, + useMobileButtons: true, + sameButtonText: "Yes", + differentButtonText: "No", + sameButtonKey: 's', + differentButtonKey: 'd' + } +} \ No newline at end of file diff --git a/packages/pattern-comparison-test/tsconfig.json b/packages/pattern-comparison-test/tsconfig.json new file mode 100644 index 00000000..33655f70 --- /dev/null +++ b/packages/pattern-comparison-test/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@jspsych/config/tsconfig.contrib.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": [ + "src", + "../cli/src/build.js" + ] +} \ No newline at end of file diff --git a/packages/spatial-nback-task/.gitignore b/packages/spatial-nback-task/.gitignore deleted file mode 100644 index 1632fe5c..00000000 --- a/packages/spatial-nback-task/.gitignore +++ /dev/null @@ -1,47 +0,0 @@ -# Node.js -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -package-lock.json -.pnpm-debug.log - -# Build artifacts -dist/ -build/ -.cache/ -.temp/ -out/ -coverage/ -*.tsbuildinfo - -# OS files -.DS_Store -Thumbs.db - -# Environment files -.env -.env.local -.env.*.local - -# IDEs and editors -.vscode/ -.idea/ -*.suo -*.ntvs* -*.njsproj -*.sln - -# Misc -logs/ -*.log -*.bak -*.swp - -# macOS Finder files -.AppleDouble -.LSOverride - -# npm pack artifacts -*.tgz \ No newline at end of file diff --git a/packages/spatial-nback-task/README.md b/packages/spatial-nback-task/README.md deleted file mode 100644 index 64374a5c..00000000 --- a/packages/spatial-nback-task/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# spatial-nback-task - -## Overview - -Deployable spatial nback timeline - -## Loading - -*Enter instructions for loading the timeline package here.* - -## Compatibility - -`spatial-nback-task` requires jsPsych v8.0.0 or later. - -## Documentation - -### createTimeline - -#### jsPsychTimelineSpatialNbackTask.createTimeline(jsPsych, { *options* }) ⇒ timeline -Description of the timeline that this plugin generates - -The following parameters can be specified in the **options** parameter. - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| parameter | type | default | description | - - -### timelineUnits - - -### utils - -## Author / Citation - -Hunter Farhat \ No newline at end of file diff --git a/packages/spatial-nback-task/examples/index.html b/packages/spatial-nback-task/examples/index.html deleted file mode 100644 index b28b95b7..00000000 --- a/packages/spatial-nback-task/examples/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Spatial N-Back Task - - - - - - - - - - - \ No newline at end of file diff --git a/packages/spatial-nback-task/package.json b/packages/spatial-nback-task/package.json deleted file mode 100644 index 562e2a88..00000000 --- a/packages/spatial-nback-task/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "spatial-nback-task", - "version": "0.0.1", - "description": "Deployable spatial nback timeline", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.browser.min.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpatialNbackTask" - }, - "repository": { - "type": "git", - "url": "https://github.com/ahfarhat/JsPsychTemplates/", - "directory": "" - }, - "keywords": [ - "jsPsych" - ], - "author": { - "name": "Hunter Farhat", - "url": "'use strict';https://github.com/ahfarhat" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/ahfarhat/JsPsychTemplates//issues" - }, - "homepage": "/spatial-nback-task/README.md", - "devDependencies": { - "@jspsych/config": "^3.2.2", - "jspsych": "^8.2.1", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - }, - "dependencies": { - "@jspsych/plugin-html-button-response": "^2.1.0", - "@jspsych/plugin-html-keyboard-response": "^2.1.0", - "jsPsych": "npm:jspsych@^8.2.1" - } -} diff --git a/packages/spatial-nback-task/src/index.ts b/packages/spatial-nback-task/src/index.ts deleted file mode 100644 index a55e9ce7..00000000 --- a/packages/spatial-nback-task/src/index.ts +++ /dev/null @@ -1,269 +0,0 @@ -import { initJsPsych, JsPsych } from "jspsych" -import jsPsychPluginSpatialNbackTs from "@jspsych/plugin-spatial-nback-ts"; -import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; -import jsPsychHtmlButtonResponse from "@jspsych/plugin-html-button-response"; - -const task_instructions = { - type: jsPsychHtmlButtonResponse, - stimulus: ` -
-

Spatial N-Back Task

-

In this task, you will see a grid with blue squares appearing in different positions.

-

Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.

-

Try to respond as quickly and accurately as possible.

-

Click the button below to begin the task.

-
- `, - choices: ['Continue'], -}; - -// Generate stimulus sequence for n-back task -function generateNBackSequence(total_trials: number, n_back_level: number, target_percentage: number, rows: number, cols: number) { - const positions: Array<{row: number, col: number}> = []; - const is_target: boolean[] = []; - - // Generate first n trials (cannot be targets) - for (let i = 0; i < n_back_level; i++) { - positions.push({ - row: Math.floor(Math.random() * rows), - col: Math.floor(Math.random() * cols) - }); - is_target.push(false); - } - - // Calculate number of targets to place - const n_targets = Math.round((target_percentage / 100) * (total_trials - n_back_level)); - let targets_placed = 0; - - // Generate remaining trials with targets - for (let i = n_back_level; i < total_trials; i++) { - const can_be_target = targets_placed < n_targets; - const should_be_target = can_be_target && Math.random() < 0.5; - - if (should_be_target) { - // Make this a target trial (same position as n trials back) - positions.push({ - row: positions[i - n_back_level].row, - col: positions[i - n_back_level].col - }); - is_target.push(true); - targets_placed++; - } else { - // Generate non-target position - let new_position: {row: number, col: number}; - do { - new_position = { - row: Math.floor(Math.random() * rows), - col: Math.floor(Math.random() * cols) - }; - } while ( - new_position.row === positions[i - n_back_level].row && - new_position.col === positions[i - n_back_level].col - ); - positions.push(new_position); - is_target.push(false); - } - } - - return { positions, is_target }; -} - -export function createSpatialNBackTimeline({ - rows = 3, - cols = 3, - n_back_level = 1, - total_trials = 20, - target_percentage = 25, - stimulus_duration = 750, - isi_duration = 250, - feedback_duration = 1000, - show_feedback = false, - show_feedback_border = false, - showFeedbackNoResponse = false, - feedbackWaitNoResponse = true, - cell_size = 150, - instructions_template = "Click the button when the position matches the one from {n} trial(s) ago", - button_text = "MATCH", - stimulus_color = "#2196F3", - correct_color = "#4CAF50", - incorrect_color = "#F44336", - include_instructions = false, - randomize_trials = false, -}: { - rows?: number, - cols?: number, - n_back_level?: number, - total_trials?: number, - target_percentage?: number, - stimulus_duration?: number, - isi_duration?: number, - feedback_duration?: number, - show_feedback?: boolean, - show_feedback_border?: boolean, - showFeedbackNoResponse?: boolean, - feedbackWaitNoResponse?: boolean, - cell_size?: number, - instructions_template?: string, - button_text?: string, - stimulus_color?: string, - correct_color?: string, - incorrect_color?: string, - include_instructions?: boolean, - randomize_trials?: boolean, -} = {}) { - - // Generate the sequence - const sequence = generateNBackSequence(total_trials, n_back_level, target_percentage, rows, cols); - - // Create individual trial objects - const trials = []; - for (let i = 0; i < total_trials; i++) { - const trial_instructions = instructions_template - .replace('{n}', n_back_level.toString()) - .replace('{trial}', (i + 1).toString()) - .replace('{total}', total_trials.toString()); - - trials.push({ - type: jsPsychPluginSpatialNbackTs, - rows: rows, - cols: cols, - stimulus_row: sequence.positions[i].row, - stimulus_col: sequence.positions[i].col, - is_target: sequence.is_target[i], - stimulus_duration: stimulus_duration, - isi_duration: isi_duration, - feedback_duration: feedback_duration, - show_feedback: show_feedback, - show_feedback_border: show_feedback_border, - showFeedbackNoResponse: showFeedbackNoResponse, - feedbackWaitNoResponse: feedbackWaitNoResponse, - cell_size: cell_size, - instructions: trial_instructions, - button_text: button_text, - stimulus_color: stimulus_color, - correct_color: correct_color, - incorrect_color: incorrect_color, - data: { - trial_number: i + 1, - n_back_level: n_back_level, - total_trials: total_trials, - task: 'spatial-nback' - } - }); - } - - // Create the main task timeline - const task_timeline = { - timeline: trials, - randomize_order: randomize_trials - }; - - // Return complete timeline with or without instructions - if (include_instructions) { - // Update instructions to show current n-back level - const custom_instructions = { - ...task_instructions, - stimulus: task_instructions.stimulus.replace( - '1 trial ago', - `${n_back_level} trial${n_back_level > 1 ? 's' : ''} ago` - ) - }; - - return { - timeline: [custom_instructions, task_timeline] - }; - } else { - return task_timeline; - } -} - -// Create a practice timeline with fewer trials -export function createPracticeTimeline(options: Parameters[0] = {}) { - return createSpatialNBackTimeline({ - ...options, - total_trials: 6, - target_percentage: 33, - show_feedback: true, - show_feedback_border: true, - include_instructions: true - }); -} - -// Create multiple n-back levels timeline -export function createMultiLevelNBackTimeline({ - n_back_levels = [1, 2], - trials_per_level = 20, - randomize_levels = false, - ...sharedOptions -}: { - n_back_levels?: number[], - trials_per_level?: number, - randomize_levels?: boolean, -} & Parameters[0] = {}) { - - const level_timelines = n_back_levels.map(level => { - return createSpatialNBackTimeline({ - ...sharedOptions, - n_back_level: level, - total_trials: trials_per_level, - include_instructions: false - }); - }); - - return { - timeline: level_timelines, - randomize_order: randomize_levels - }; -} - -// Utility functions for common configurations -export const presetConfigurations = { - easy: () => createSpatialNBackTimeline({ - n_back_level: 1, - total_trials: 20, - target_percentage: 30, - show_feedback: true - }), - - medium: () => createSpatialNBackTimeline({ - n_back_level: 2, - total_trials: 30, - target_percentage: 25, - show_feedback: false - }), - - hard: () => createSpatialNBackTimeline({ - n_back_level: 3, - total_trials: 40, - target_percentage: 20, - show_feedback: false, - rows: 4, - cols: 4 - }), - - research: () => createMultiLevelNBackTimeline({ - n_back_levels: [1, 2, 3], - trials_per_level: 50, - target_percentage: 25, - show_feedback: false, - randomize_levels: true - }) -}; - -// Export individual components for custom use -export { task_instructions, generateNBackSequence }; - -// Export default timeline creator -export default createSpatialNBackTimeline; - -export const timelineUnits = { - createPracticeTimeline, - createSpatialNBackTimeline, - createMultiLevelNBackTimeline, -}; - -export const utils = { - presetConfigurations, - generateNBackSequence, - task_instructions -} \ No newline at end of file diff --git a/packages/spatial-nback/.gitignore b/packages/spatial-nback/.gitignore deleted file mode 100644 index 1632fe5c..00000000 --- a/packages/spatial-nback/.gitignore +++ /dev/null @@ -1,47 +0,0 @@ -# Node.js -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -package-lock.json -.pnpm-debug.log - -# Build artifacts -dist/ -build/ -.cache/ -.temp/ -out/ -coverage/ -*.tsbuildinfo - -# OS files -.DS_Store -Thumbs.db - -# Environment files -.env -.env.local -.env.*.local - -# IDEs and editors -.vscode/ -.idea/ -*.suo -*.ntvs* -*.njsproj -*.sln - -# Misc -logs/ -*.log -*.bak -*.swp - -# macOS Finder files -.AppleDouble -.LSOverride - -# npm pack artifacts -*.tgz \ No newline at end of file diff --git a/packages/spatial-nback/docs/spatial-nback.md b/packages/spatial-nback/docs/spatial-nback.md index a0e3d2db..c4278322 100644 --- a/packages/spatial-nback/docs/spatial-nback.md +++ b/packages/spatial-nback/docs/spatial-nback.md @@ -20,16 +20,16 @@ Parameter | Type | Default Value | Description ----------|------|---------------|------------ rows | number | 3 | Number of rows in the grid cols | number | 3 | Number of columns in the grid -n_back_level | number | 1 | The n-back level (how many trials back to match) +n_back | number | 1 | The n-back level (how many trials back to match) total_trials | number | 20 | Total number of trials in the timeline target_percentage | number | 25 | Percentage of trials that are targets stimulus_duration | number | 750 | Duration (ms) the stimulus is shown isi_duration | number | 250 | Inter-stimulus interval (ms) feedback_duration | number | 1000 | Duration (ms) of feedback display -show_feedback | boolean | false | Whether to show feedback after each trial +show_feedback_text | boolean | false | Whether to show feedback after each trial show_feedback_border | boolean | false | Whether to highlight the grid border for feedback -showFeedbackNoResponse | boolean | false | Show feedback if no response is made -feedbackWaitNoResponse | boolean | true | Wait for feedback even if no response +show_feedback_no_click | boolean | false | Show feedback if no response is made +feedback_wait_no_click | boolean | true | Wait for feedback even if no response cell_size | number | 150 | Size (px) of each grid cell instructions_trial | string | "Click the button when the position matches the one from {n} trial(s) ago" | Instructions template for each trial button_text | string | "MATCH" | Text for the match button @@ -54,14 +54,14 @@ var trial = { Parameter | Type | Default Value | Description ----------|------|---------------|------------ -See above | | | (All initialization parameters can also be set per trial) +See above | | | (All initialization parameters can also be set per trial, look at @jspsych-contrib/plugin-spatial-nback for more info) ## Data Generated Name | Type | Value -----|------|------ trial_number | number | The trial index (1-based) -n_back_level | number | The n-back level for the timeline +n_back | number | The n-back level for the timeline total_trials | number | Total number of trials in the timeline task | string | "spatial-nback" response | string/number | Participant's response (if applicable) @@ -87,7 +87,7 @@ Predefined configurations: - `hard()` - `research()` -### generateNBackSequence(total_trials, n_back_level, target_percentage, rows, cols) +### generateNBackSequence(total_trials, n_back, target_percentage, rows, cols) Generates the sequence of positions and target flags for the task. ### instrictions_template diff --git a/packages/spatial-nback/examples/index.html b/packages/spatial-nback/examples/index.html index 9ce28a66..c3cddda3 100644 --- a/packages/spatial-nback/examples/index.html +++ b/packages/spatial-nback/examples/index.html @@ -21,22 +21,22 @@ } }); - const instructions_trial = jsPsychTimelineSpatialNback.utils.instrictions_template; + const instructions_trial = jsPsychTimelineSpatialNback.utils.instructions_template; // Create a custom timeline const spatial1Back = jsPsychTimelineSpatialNback.createTimeline({ rows: 3, cols: 3, - n_back_level: 1, + n_back: 1, total_trials: 2, target_percentage: 50, stimulus_duration: 750, isi_duration: 250, feedback_duration: 1000, - show_feedback: true, + show_feedback_text: true, show_feedback_border: true, - showFeedbackNoResponse: true, - feedbackWaitNoResponse: true, + show_feedback_no_click : true, + feedback_wait_no_click: true, cell_size: 150, instructions_trial: "Click the button when the position matches the one from {n} trial(s) ago", button_text: "MATCH", @@ -52,11 +52,12 @@ // Or create a multi-level experiment // const timeline = createMultiLevelNBackTimeline({ - // n_back_levels: [1, 2, 3], + // n_backs: [1, 2, 3], // trials_per_level: 20 // }); + const spatial1BackTrials = spatial1Back.timeline; - jsPsych.run([instructions_trial, spatial1Back]); + jsPsych.run([instructions_trial, spatial1BackTrials]); \ No newline at end of file diff --git a/packages/spatial-nback/jest.config.cjs b/packages/spatial-nback/jest.config.cjs new file mode 100644 index 00000000..bd1aacef --- /dev/null +++ b/packages/spatial-nback/jest.config.cjs @@ -0,0 +1 @@ +module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/spatial-nback-task/package-lock.json b/packages/spatial-nback/package-lock.json similarity index 100% rename from packages/spatial-nback-task/package-lock.json rename to packages/spatial-nback/package-lock.json diff --git a/packages/spatial-nback/src/index.spec copy.ts b/packages/spatial-nback/src/index.spec copy.ts new file mode 100644 index 00000000..35e9dd34 --- /dev/null +++ b/packages/spatial-nback/src/index.spec copy.ts @@ -0,0 +1,188 @@ +import { startTimeline } from "@jspsych/test-utils"; +import jsPsychPluginSpatialNback from "@jspsych/plugin-spatial-nback"; +import { createTimeline, createPracticeTimeline, createMultiLevelNBackTimeline, presetConfigurations } from "./index"; + +describe('Spatial N-Back Timeline', () => { + describe('createTimeline', () => { + it('should return a timeline object with correct structure', () => { + const timeline = createTimeline({ total_trials: 5 }); + expect(timeline).toHaveProperty('timeline'); + expect(Array.isArray(timeline.timeline)).toBe(true); + expect(timeline.timeline.length).toBe(5); + }); + + it('should use default parameters when none provided', () => { + const timeline = createTimeline(); + expect(timeline.timeline.length).toBe(20); // default total_trials + expect(timeline.timeline[0].rows).toBe(3); + expect(timeline.timeline[0].cols).toBe(3); + expect(timeline.timeline[0].data.n_back).toBe(1); + }); + + it('should set randomize_order when randomize_trials is true', () => { + const timeline = createTimeline({ + total_trials: 5, + randomize_trials: true + }); + expect(timeline.randomize_order).toBe(true); + }); + + it('should not set randomize_order when randomize_trials is false', () => { + const timeline = createTimeline({ + total_trials: 5, + randomize_trials: false + }); + // Should be undefined or falsy + expect(timeline.randomize_order).toBeFalsy(); + }); + + it('should include instructions trial when include_instructions is true', () => { + const timeline = createTimeline({ + total_trials: 5, + include_instructions: true + }); + // The first trial should be the instructions + expect(timeline.timeline[0].stimulus).toContain('Spatial N-Back Task'); + // The rest should be the actual trials (timeline structure has instructions + nested timeline) + expect(timeline.timeline.length).toBe(2); // Instructions + nested timeline + expect(timeline.timeline[1].timeline.length).toBe(5); // Nested timeline should have 5 trials + }); + + it('should pass custom parameters to trial objects', () => { + const customParams = { + total_trials: 3, + rows: 4, + cols: 5, + stimulus_color: '#ff0000', + button_text: 'CUSTOM', + n_back: 2 + }; + const timeline = createTimeline(customParams); + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].cols).toBe(5); + expect(timeline.timeline[0].stimulus_color).toBe('#ff0000'); + expect(timeline.timeline[0].button_text).toBe('CUSTOM'); + expect(timeline.timeline[0].data.n_back).toBe(2); + }); + + it('should generate valid target sequences', () => { + const timeline = createTimeline({ + total_trials: 10, + n_back: 2, + target_percentage: 30 + }); + // Check that early trials are not targets (can't be n-back matches) + expect(timeline.timeline[0].is_target).toBe(false); + expect(timeline.timeline[1].is_target).toBe(false); + // Check that some trials are targets + const targets = timeline.timeline.filter(trial => trial.is_target); + expect(targets.length).toBeGreaterThan(0); + expect(targets.length).toBeLessThanOrEqual(8); // Max possible with n_back=2 + }); + }); + + describe('createPracticeTimeline', () => { + it('should create practice timeline with correct defaults', () => { + const timeline = createPracticeTimeline(); + // The rest should be the actual trials (timeline structure has instructions + nested timeline) + expect(timeline.timeline.length).toBe(2); // Instructions + nested timeline + expect(timeline.timeline[1].timeline.length).toBe(6); // Nested timeline should have 6 trials + expect(timeline.timeline[1].timeline[0].show_feedback_text).toBe(true); + expect(timeline.timeline[1].timeline[0].show_feedback_border).toBe(true); + }); + + it('should override defaults with custom parameters', () => { + const timeline = createPracticeTimeline({ + rows: 4, + stimulus_color: '#00ff00' + }); + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].stimulus_color).toBe('#00ff00'); + expect(timeline.timeline[0].show_feedback_text).toBe(true); // Should still be true + }); + }); + + describe('createMultiLevelNBackTimeline', () => { + it('should create timeline with multiple n-back levels', () => { + const timeline = createMultiLevelNBackTimeline({ + n_backs: [1, 2, 3], + trials_per_level: 5 + }); + expect(timeline.timeline.length).toBe(3); + expect(timeline.timeline[0].timeline.length).toBe(5); + expect(timeline.timeline[1].timeline.length).toBe(5); + expect(timeline.timeline[2].timeline.length).toBe(5); + }); + + it('should set correct n_back for each sub-timeline', () => { + const timeline = createMultiLevelNBackTimeline({ + n_backs: [1, 2, 3], + trials_per_level: 3 + }); + expect(timeline.timeline[0].timeline[0].data.n_back).toBe(1); + expect(timeline.timeline[1].timeline[0].data.n_back).toBe(2); + expect(timeline.timeline[2].timeline[0].data.n_back).toBe(3); + }); + + it('should randomize level order when randomize_levels is true', () => { + const timeline = createMultiLevelNBackTimeline({ + n_backs: [1, 2], + randomize_levels: true + }); + expect(timeline.randomize_order).toBe(true); + }); + }); + + describe('presetConfigurations', () => { + it('easy preset should have correct settings', () => { + const timeline = presetConfigurations.easy(); + expect(timeline.timeline[0].data.n_back).toBe(1); + expect(timeline.timeline[0].show_feedback_text).toBe(true); + expect(timeline.timeline[0].total_trials).toBe(15); + }); + + it('medium preset should have correct settings', () => { + const timeline = presetConfigurations.medium(); + expect(timeline.timeline[0].data.n_back).toBe(2); + expect(timeline.timeline[0].show_feedback_text).toBe(false); + expect(timeline.timeline[0].total_trials).toBe(20); + }); + + it('hard preset should have correct settings', () => { + const timeline = presetConfigurations.hard(); + expect(timeline.timeline[0].data.n_back).toBe(3); + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].cols).toBe(4); + expect(timeline.timeline[0].total_trials).toBe(25); + }); + + it('research preset should create multi-level timeline', () => { + const timeline = presetConfigurations.research(); + expect(Array.isArray(timeline.timeline)).toBe(true); + expect(timeline.timeline.length).toBe(3); // 1-back, 2-back, 3-back + }); + }); + + describe('parameter validation', () => { + it('should handle edge case parameters', () => { + const timeline = createTimeline({ + total_trials: 1, + target_percentage: 0, + n_back: 1 + }); + expect(timeline.timeline.length).toBe(1); + expect(timeline.timeline[0].is_target).toBe(false); // Can't be target with n_back=1 + }); + + it('should handle high target percentage correctly', () => { + const timeline = createTimeline({ + total_trials: 10, + target_percentage: 90, + n_back: 1 + }); + // Should not exceed maximum possible targets + const targets = timeline.timeline.filter(trial => trial.is_target); + expect(targets.length).toBeLessThanOrEqual(9); // Max with n_back=1 and 10 trials + }); + }); +}); \ No newline at end of file diff --git a/packages/spatial-nback/src/index.spec.ts b/packages/spatial-nback/src/index.spec.ts index 4d1c4fb6..cc6d0bc0 100644 --- a/packages/spatial-nback/src/index.spec.ts +++ b/packages/spatial-nback/src/index.spec.ts @@ -1,202 +1,195 @@ import { startTimeline } from "@jspsych/test-utils"; -import jsPsychPluginSpatialNback from "@jspsych/plugin-spatial-nback"; -import { JsPsych } from "jspsych"; - -describe('Spatial N-Back Timeline', () => { - describe('createTimeline', () => { - it('should return a timeline object with correct structure', () => { - const timeline = createTimeline({ total_trials: 5 }); - - expect(timeline).toHaveProperty('timeline'); - expect(Array.isArray(timeline.timeline)).toBe(true); - expect(timeline.timeline.length).toBe(5); - }); +import jsPsychPluginSpatialNback from "@jspsych-contrib/plugin-spatial-nback"; +import { createTimeline, createPracticeTimeline, createMultiLevelNBackTimeline, presetConfigurations } from "./index"; + +/* Test suite for Spatial N-Back Timeline + In-depth HTML testing is in the Spatial N-Back plugin. */ +describe('createTimeline', () => { + it('should create timeline with default parameters', () => { + const timeline = createTimeline(); + expect(timeline.timeline).toHaveLength(20); + expect(timeline.randomize_order).toBe(false); + expect(timeline.timeline[0].rows).toBe(3); + expect(timeline.timeline[0].cols).toBe(3); + expect(timeline.timeline[0].data.n_back).toBe(1); + }); - it('should use default parameters when none provided', () => { - const timeline = createTimeline(); - - expect(timeline.timeline.length).toBe(20); // default total_trials - expect(timeline.timeline[0]).toHaveProperty('rows', 3); - expect(timeline.timeline[0]).toHaveProperty('cols', 3); - expect(timeline.timeline[0]).toHaveProperty('n_back_level', 1); - }); + it('should create timeline with custom total_trials', () => { + const timeline = createTimeline({ total_trials: 25 }); + expect(timeline.timeline).toHaveLength(25); + }); - it('should set randomize_order when randomize_trials is true', () => { - const timeline = createTimeline({ - total_trials: 5, - randomize_trials: true - }); - - expect(timeline.randomize_order).toBe(true); - }); + it('should create timeline with custom grid dimensions', () => { + const timeline = createTimeline({ rows: 4, cols: 5 }); + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].cols).toBe(5); + }); - it('should not set randomize_order when randomize_trials is false', () => { - const timeline = createTimeline({ - total_trials: 5, - randomize_trials: false - }); - - expect(timeline.randomize_order).toBeUndefined(); + it('should set correct n_back in trial data', () => { + const timeline = createTimeline({ n_back: 3 }); + timeline.timeline.forEach(trial => { + expect(trial.data.n_back).toBe(3); }); + }); - it('should include instructions trial when include_instructions is true', () => { - const timeline = createTimeline({ - total_trials: 5, - include_instructions: true - }); - - // First trial should be instructions - expect(timeline.timeline[0].stimulus).toContain('Spatial N-Back Task'); - expect(timeline.timeline.length).toBe(6); // 5 trials + 1 instruction - }); + it('should include instructions when specified', () => { + const timeline = createTimeline({ include_instructions: true }); + expect(timeline.timeline).toHaveLength(2); + expect(timeline.timeline[0].choices).toEqual(['Continue']); + }); - it('should pass custom parameters to trial objects', () => { - const customParams = { - total_trials: 3, - rows: 4, - cols: 5, - stimulus_color: '#ff0000', - button_text: 'CUSTOM', - n_back_level: 2 - }; - - const timeline = createTimeline(customParams); - - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].cols).toBe(5); - expect(timeline.timeline[0].stimulus_color).toBe('#ff0000'); - expect(timeline.timeline[0].button_text).toBe('CUSTOM'); - }); + it('should randomize trials when specified', () => { + const timeline = createTimeline({ randomize_trials: true }); + expect(timeline.randomize_order).toBe(true); + }); - it('should generate valid target sequences', () => { - const timeline = createTimeline({ - total_trials: 10, - n_back_level: 2, - target_percentage: 30 - }); - - // Check that early trials are not targets (can't be n-back matches) - expect(timeline.timeline[0].is_target).toBe(false); - expect(timeline.timeline[1].is_target).toBe(false); - - // Check that some trials are targets - const targets = timeline.timeline.filter(trial => trial.is_target); - expect(targets.length).toBeGreaterThan(0); - expect(targets.length).toBeLessThanOrEqual(8); // Max possible with n_back_level=2 + it('should set correct trial properties', () => { + const timeline = createTimeline({ + stimulus_duration: 1000, + button_text: 'CLICK', + cell_size: 200 }); + + expect(timeline.timeline[0].stimulus_duration).toBe(1000); + expect(timeline.timeline[0].button_text).toBe('CLICK'); + expect(timeline.timeline[0].cell_size).toBe(200); }); - describe('createPracticeTimeline', () => { - it('should create practice timeline with correct defaults', () => { - const timeline = createPracticeTimeline(); - - expect(timeline.timeline.length).toBe(6); - expect(timeline.timeline[0].show_feedback).toBe(true); - expect(timeline.timeline[0].target_percentage).toBe(33); + it('should generate correct trial numbers in data', () => { + const timeline = createTimeline({ total_trials: 5 }); + timeline.timeline.forEach((trial, index) => { + expect(trial.data.trial_number).toBe(index + 1); + expect(trial.data.task).toBe('spatial-nback'); }); + }); +}); - it('should override defaults with custom parameters', () => { - const timeline = createPracticeTimeline({ - rows: 4, - stimulus_color: '#00ff00' - }); - - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].stimulus_color).toBe('#00ff00'); - expect(timeline.timeline[0].show_feedback).toBe(true); // Should still be true - }); +describe('createPracticeTimeline', () => { + it('should create practice timeline with default 6 trials', () => { + const timeline = createPracticeTimeline(); + expect(timeline.timeline).toHaveLength(2); // instructions + task + expect(timeline.timeline[1].timeline).toHaveLength(6); }); - describe('createMultiLevelNBackTimeline', () => { - it('should create timeline with multiple n-back levels', () => { - const timeline = createMultiLevelNBackTimeline({ - n_back_levels: [1, 2, 3], - trials_per_level: 5 - }); - - expect(timeline.timeline.length).toBe(3); - expect(timeline.timeline[0].timeline.length).toBe(5); - expect(timeline.timeline[1].timeline.length).toBe(5); - expect(timeline.timeline[2].timeline.length).toBe(5); - }); + it('should enable feedback by default', () => { + const timeline = createPracticeTimeline(); + const taskTimeline = timeline.timeline[1]; + expect(taskTimeline.timeline[0].show_feedback_text).toBe(true); + expect(taskTimeline.timeline[0].show_feedback_border).toBe(true); + }); - it('should set correct n_back_level for each sub-timeline', () => { - const timeline = createMultiLevelNBackTimeline({ - n_back_levels: [1, 2, 3], - trials_per_level: 3 - }); - - expect(timeline.timeline[0].timeline[0].n_back_level).toBe(1); - expect(timeline.timeline[1].timeline[0].n_back_level).toBe(2); - expect(timeline.timeline[2].timeline[0].n_back_level).toBe(3); - }); + it('should accept custom options', () => { + const timeline = createPracticeTimeline({ n_back: 2 }); + const taskTimeline = timeline.timeline[1]; + expect(taskTimeline.timeline[0].data.n_back).toBe(2); + }); +}); + +describe('createMultiLevelNBackTimeline', () => { + it('should create timeline with multiple levels', () => { + const timeline = createMultiLevelNBackTimeline({ + n_backs: [1, 2, 3], + trials_per_level: 10 + }); + + expect(timeline.timeline).toHaveLength(3); + expect(timeline.timeline[0].timeline).toHaveLength(10); + expect(timeline.timeline[1].timeline).toHaveLength(10); + expect(timeline.timeline[2].timeline).toHaveLength(10); + }); - it('should randomize level order when randomize_levels is true', () => { - const timeline = createMultiLevelNBackTimeline({ - n_back_levels: [1, 2], - randomize_levels: true - }); - - expect(timeline.randomize_order).toBe(true); + it('should set correct n_back for each level', () => { + const timeline = createMultiLevelNBackTimeline({ + n_backs: [1, 3], + trials_per_level: 5 }); + + expect(timeline.timeline[0].timeline[0].data.n_back).toBe(1); + expect(timeline.timeline[1].timeline[0].data.n_back).toBe(3); }); - describe('presetConfigurations', () => { - it('easy preset should have correct settings', () => { - const timeline = presetConfigurations.easy(); - - expect(timeline.timeline[0].n_back_level).toBe(1); - expect(timeline.timeline[0].show_feedback).toBe(true); - expect(timeline.timeline[0].total_trials).toBe(15); + it('should randomize levels when specified', () => { + const timeline = createMultiLevelNBackTimeline({ + randomize_levels: true }); + + expect(timeline.randomize_order).toBe(true); + }); +}); + +describe('presetConfigurations', () => { + it('should create easy preset with correct parameters', () => { + const timeline = presetConfigurations.easy(); + expect(timeline.timeline).toHaveLength(20); + expect(timeline.timeline[0].data.n_back).toBe(1); + expect(timeline.timeline[0].show_feedback_text).toBe(true); + }); - it('medium preset should have correct settings', () => { - const timeline = presetConfigurations.medium(); - - expect(timeline.timeline[0].n_back_level).toBe(2); - expect(timeline.timeline[0].show_feedback).toBe(false); - expect(timeline.timeline[0].total_trials).toBe(20); - }); + it('should create medium preset with correct parameters', () => { + const timeline = presetConfigurations.medium(); + expect(timeline.timeline).toHaveLength(30); + expect(timeline.timeline[0].data.n_back).toBe(2); + expect(timeline.timeline[0].show_feedback_text).toBe(false); + }); - it('hard preset should have correct settings', () => { - const timeline = presetConfigurations.hard(); - - expect(timeline.timeline[0].n_back_level).toBe(3); - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].cols).toBe(4); - expect(timeline.timeline[0].total_trials).toBe(25); - }); + it('should create hard preset with correct parameters', () => { + const timeline = presetConfigurations.hard(); + expect(timeline.timeline).toHaveLength(40); + expect(timeline.timeline[0].data.n_back).toBe(3); + expect(timeline.timeline[0].rows).toBe(4); + expect(timeline.timeline[0].cols).toBe(4); + }); - it('research preset should create multi-level timeline', () => { - const timeline = presetConfigurations.research(); - - expect(Array.isArray(timeline.timeline)).toBe(true); - expect(timeline.timeline.length).toBe(3); // 1-back, 2-back, 3-back + it('should create research preset with multi-level timeline', () => { + const timeline = presetConfigurations.research(); + expect(timeline.timeline).toHaveLength(3); + expect(timeline.randomize_order).toBe(true); + + // Check each level has 50 trials + timeline.timeline.forEach(levelTimeline => { + expect(levelTimeline.timeline).toHaveLength(50); }); }); +}); + +describe('timeline instruction customization', () => { + it('should update instructions for different n_backs', () => { + const timeline1 = createTimeline({ n_back: 1, include_instructions: true }); + const timeline2 = createTimeline({ n_back: 2, include_instructions: true }); + + expect(timeline1.timeline[0].stimulus).toContain('1 trial ago'); + expect(timeline2.timeline[0].stimulus).toContain('2 trials ago'); + }); - describe('parameter validation', () => { - it('should handle edge case parameters', () => { - const timeline = createTimeline({ - total_trials: 1, - target_percentage: 0, - n_back_level: 1 - }); - - expect(timeline.timeline.length).toBe(1); - expect(timeline.timeline[0].is_target).toBe(false); // Can't be target with n_back_level=1 + it('should replace placeholders in trial instructions', () => { + const timeline = createTimeline({ + instructions_trial: "Level {n_back}, Trial {trial} of {total}", + n_back: 2, + total_trials: 5 }); + + expect(timeline.timeline[0].instructions).toBe("Level 2, Trial 1 of 5"); + expect(timeline.timeline[4].instructions).toBe("Level 2, Trial 5 of 5"); + }); +}); + +describe('timeline structure validation', () => { + it('should ensure first n trials are never targets', () => { + const timeline = createTimeline({ n_back: 3, total_trials: 10 }); + + expect(timeline.timeline[0].is_target).toBe(false); + expect(timeline.timeline[1].is_target).toBe(false); + expect(timeline.timeline[2].is_target).toBe(false); + }); - it('should handle high target percentage correctly', () => { - const timeline = createTimeline({ - total_trials: 10, - target_percentage: 90, - n_back_level: 1 - }); - - // Should not exceed maximum possible targets - const targets = timeline.timeline.filter(trial => trial.is_target); - expect(targets.length).toBeLessThanOrEqual(9); // Max with n_back_level=1 and 10 trials + it('should maintain consistent data structure across trials', () => { + const timeline = createTimeline({ total_trials: 5 }); + + timeline.timeline.forEach(trial => { + expect(trial.data).toHaveProperty('trial_number'); + expect(trial.data).toHaveProperty('n_back'); + expect(trial.data).toHaveProperty('total_trials'); + expect(trial.data).toHaveProperty('task'); }); }); -}); \ No newline at end of file +}); diff --git a/packages/spatial-nback/src/index.ts b/packages/spatial-nback/src/index.ts index c0224c6c..dff07c25 100644 --- a/packages/spatial-nback/src/index.ts +++ b/packages/spatial-nback/src/index.ts @@ -3,7 +3,7 @@ import jsPsychPluginSpatialNback from "@jspsych/plugin-spatial-nback"; import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; import jsPsychHtmlButtonResponse from "@jspsych/plugin-html-button-response"; -const instrictions_template = { +const instructions_template = { type: jsPsychHtmlButtonResponse, stimulus: `
@@ -18,12 +18,12 @@ const instrictions_template = { }; // Generate stimulus sequence for n-back task -function generateNBackSequence(total_trials: number, n_back_level: number, target_percentage: number, rows: number, cols: number) { +function generateNBackSequence(total_trials: number, n_back: number, target_percentage: number, rows: number, cols: number) { const positions: Array<{row: number, col: number}> = []; const is_target: boolean[] = []; // Generate first n trials (cannot be targets) - for (let i = 0; i < n_back_level; i++) { + for (let i = 0; i < n_back; i++) { positions.push({ row: Math.floor(Math.random() * rows), col: Math.floor(Math.random() * cols) @@ -32,19 +32,19 @@ function generateNBackSequence(total_trials: number, n_back_level: number, targe } // Calculate number of targets to place - const n_targets = Math.round((target_percentage / 100) * (total_trials - n_back_level)); + const n_targets = Math.round((target_percentage / 100) * (total_trials - n_back)); let targets_placed = 0; // Generate remaining trials with targets - for (let i = n_back_level; i < total_trials; i++) { + for (let i = n_back; i < total_trials; i++) { const can_be_target = targets_placed < n_targets; const should_be_target = can_be_target && Math.random() < 0.5; if (should_be_target) { // Make this a target trial (same position as n trials back) positions.push({ - row: positions[i - n_back_level].row, - col: positions[i - n_back_level].col + row: positions[i - n_back].row, + col: positions[i - n_back].col }); is_target.push(true); targets_placed++; @@ -57,8 +57,8 @@ function generateNBackSequence(total_trials: number, n_back_level: number, targe col: Math.floor(Math.random() * cols) }; } while ( - new_position.row === positions[i - n_back_level].row && - new_position.col === positions[i - n_back_level].col + new_position.row === positions[i - n_back].row && + new_position.col === positions[i - n_back].col ); positions.push(new_position); is_target.push(false); @@ -71,16 +71,16 @@ function generateNBackSequence(total_trials: number, n_back_level: number, targe export function createTimeline({ rows = 3, cols = 3, - n_back_level = 1, + n_back = 1, total_trials = 20, target_percentage = 25, stimulus_duration = 750, isi_duration = 250, feedback_duration = 1000, - show_feedback = false, + show_feedback_text = false, show_feedback_border = false, - showFeedbackNoResponse = false, - feedbackWaitNoResponse = true, + show_feedback_no_click = false, + feedback_wait_no_click = true, cell_size = 150, instructions_trial = "Click the button when the position matches the one from {n} trial(s) ago", button_text = "MATCH", @@ -92,16 +92,16 @@ export function createTimeline({ }: { rows?: number, cols?: number, - n_back_level?: number, + n_back?: number, total_trials?: number, target_percentage?: number, stimulus_duration?: number, isi_duration?: number, feedback_duration?: number, - show_feedback?: boolean, + show_feedback_text?: boolean, show_feedback_border?: boolean, - showFeedbackNoResponse?: boolean, - feedbackWaitNoResponse?: boolean, + show_feedback_no_click ?: boolean, + feedback_wait_no_click?: boolean, cell_size?: number, instructions_trial?: string, button_text?: string, @@ -113,13 +113,13 @@ export function createTimeline({ } = {}) { // Generate the sequence - const sequence = generateNBackSequence(total_trials, n_back_level, target_percentage, rows, cols); + const sequence = generateNBackSequence(total_trials, n_back, target_percentage, rows, cols); // Create individual trial objects const trials = []; for (let i = 0; i < total_trials; i++) { const trial_instructions = instructions_trial - .replace('{n}', n_back_level.toString()) + .replace('{n_back}', n_back.toString()) .replace('{trial}', (i + 1).toString()) .replace('{total}', total_trials.toString()); @@ -133,10 +133,10 @@ export function createTimeline({ stimulus_duration: stimulus_duration, isi_duration: isi_duration, feedback_duration: feedback_duration, - show_feedback: show_feedback, + show_feedback_text: show_feedback_text, show_feedback_border: show_feedback_border, - showFeedbackNoResponse: showFeedbackNoResponse, - feedbackWaitNoResponse: feedbackWaitNoResponse, + show_feedback_no_click : show_feedback_no_click , + feedback_wait_no_click: feedback_wait_no_click, cell_size: cell_size, instructions: trial_instructions, button_text: button_text, @@ -145,7 +145,7 @@ export function createTimeline({ incorrect_color: incorrect_color, data: { trial_number: i + 1, - n_back_level: n_back_level, + n_back: n_back, total_trials: total_trials, task: 'spatial-nback' } @@ -162,16 +162,18 @@ export function createTimeline({ if (include_instructions) { // Update instructions to show current n-back level const custom_instructions = { - ...instrictions_template, - stimulus: instrictions_template.stimulus.replace( + ...instructions_template, + stimulus: instructions_template.stimulus.replace( '1 trial ago', - `${n_back_level} trial${n_back_level > 1 ? 's' : ''} ago` + `${n_back} trial${n_back > 1 ? 's' : ''} ago` ) }; - return { + const nested_timeline = { timeline: [custom_instructions, task_timeline] }; + return nested_timeline; + } else { return task_timeline; } @@ -182,8 +184,8 @@ export function createPracticeTimeline(options: Parameters[0] = {}) { - const level_timelines = n_back_levels.map(level => { + const level_timelines = n_backs.map(level => { return createTimeline({ ...sharedOptions, - n_back_level: level, + n_back: level, total_trials: trials_per_level, include_instructions: false }); @@ -219,39 +221,39 @@ export function createMultiLevelNBackTimeline({ // Utility functions for common configurations export const presetConfigurations = { easy: () => createTimeline({ - n_back_level: 1, + n_back: 1, total_trials: 20, target_percentage: 30, - show_feedback: true + show_feedback_text: true }), medium: () => createTimeline({ - n_back_level: 2, + n_back: 2, total_trials: 30, target_percentage: 25, - show_feedback: false + show_feedback_text: false }), hard: () => createTimeline({ - n_back_level: 3, + n_back: 3, total_trials: 40, target_percentage: 20, - show_feedback: false, + show_feedback_text: false, rows: 4, cols: 4 }), research: () => createMultiLevelNBackTimeline({ - n_back_levels: [1, 2, 3], + n_backs: [1, 2, 3], trials_per_level: 50, target_percentage: 25, - show_feedback: false, + show_feedback_text: false, randomize_levels: true }) }; // Export individual components for custom use -export { instrictions_template, generateNBackSequence }; +export { instructions_template, generateNBackSequence }; // Export default timeline creator export default createTimeline; @@ -265,5 +267,5 @@ export const timelineUnits = { export const utils = { presetConfigurations, generateNBackSequence, - instrictions_template + instructions_template } \ No newline at end of file From 658d3e4c5ab3fa859b749e5d920b7c4d686f42c7 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 16 Jun 2025 15:47:35 -0400 Subject: [PATCH 09/37] completed test suite, all tests pass. --- packages/spatial-nback/src/index.spec copy.ts | 188 ------------------ 1 file changed, 188 deletions(-) delete mode 100644 packages/spatial-nback/src/index.spec copy.ts diff --git a/packages/spatial-nback/src/index.spec copy.ts b/packages/spatial-nback/src/index.spec copy.ts deleted file mode 100644 index 35e9dd34..00000000 --- a/packages/spatial-nback/src/index.spec copy.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { startTimeline } from "@jspsych/test-utils"; -import jsPsychPluginSpatialNback from "@jspsych/plugin-spatial-nback"; -import { createTimeline, createPracticeTimeline, createMultiLevelNBackTimeline, presetConfigurations } from "./index"; - -describe('Spatial N-Back Timeline', () => { - describe('createTimeline', () => { - it('should return a timeline object with correct structure', () => { - const timeline = createTimeline({ total_trials: 5 }); - expect(timeline).toHaveProperty('timeline'); - expect(Array.isArray(timeline.timeline)).toBe(true); - expect(timeline.timeline.length).toBe(5); - }); - - it('should use default parameters when none provided', () => { - const timeline = createTimeline(); - expect(timeline.timeline.length).toBe(20); // default total_trials - expect(timeline.timeline[0].rows).toBe(3); - expect(timeline.timeline[0].cols).toBe(3); - expect(timeline.timeline[0].data.n_back).toBe(1); - }); - - it('should set randomize_order when randomize_trials is true', () => { - const timeline = createTimeline({ - total_trials: 5, - randomize_trials: true - }); - expect(timeline.randomize_order).toBe(true); - }); - - it('should not set randomize_order when randomize_trials is false', () => { - const timeline = createTimeline({ - total_trials: 5, - randomize_trials: false - }); - // Should be undefined or falsy - expect(timeline.randomize_order).toBeFalsy(); - }); - - it('should include instructions trial when include_instructions is true', () => { - const timeline = createTimeline({ - total_trials: 5, - include_instructions: true - }); - // The first trial should be the instructions - expect(timeline.timeline[0].stimulus).toContain('Spatial N-Back Task'); - // The rest should be the actual trials (timeline structure has instructions + nested timeline) - expect(timeline.timeline.length).toBe(2); // Instructions + nested timeline - expect(timeline.timeline[1].timeline.length).toBe(5); // Nested timeline should have 5 trials - }); - - it('should pass custom parameters to trial objects', () => { - const customParams = { - total_trials: 3, - rows: 4, - cols: 5, - stimulus_color: '#ff0000', - button_text: 'CUSTOM', - n_back: 2 - }; - const timeline = createTimeline(customParams); - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].cols).toBe(5); - expect(timeline.timeline[0].stimulus_color).toBe('#ff0000'); - expect(timeline.timeline[0].button_text).toBe('CUSTOM'); - expect(timeline.timeline[0].data.n_back).toBe(2); - }); - - it('should generate valid target sequences', () => { - const timeline = createTimeline({ - total_trials: 10, - n_back: 2, - target_percentage: 30 - }); - // Check that early trials are not targets (can't be n-back matches) - expect(timeline.timeline[0].is_target).toBe(false); - expect(timeline.timeline[1].is_target).toBe(false); - // Check that some trials are targets - const targets = timeline.timeline.filter(trial => trial.is_target); - expect(targets.length).toBeGreaterThan(0); - expect(targets.length).toBeLessThanOrEqual(8); // Max possible with n_back=2 - }); - }); - - describe('createPracticeTimeline', () => { - it('should create practice timeline with correct defaults', () => { - const timeline = createPracticeTimeline(); - // The rest should be the actual trials (timeline structure has instructions + nested timeline) - expect(timeline.timeline.length).toBe(2); // Instructions + nested timeline - expect(timeline.timeline[1].timeline.length).toBe(6); // Nested timeline should have 6 trials - expect(timeline.timeline[1].timeline[0].show_feedback_text).toBe(true); - expect(timeline.timeline[1].timeline[0].show_feedback_border).toBe(true); - }); - - it('should override defaults with custom parameters', () => { - const timeline = createPracticeTimeline({ - rows: 4, - stimulus_color: '#00ff00' - }); - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].stimulus_color).toBe('#00ff00'); - expect(timeline.timeline[0].show_feedback_text).toBe(true); // Should still be true - }); - }); - - describe('createMultiLevelNBackTimeline', () => { - it('should create timeline with multiple n-back levels', () => { - const timeline = createMultiLevelNBackTimeline({ - n_backs: [1, 2, 3], - trials_per_level: 5 - }); - expect(timeline.timeline.length).toBe(3); - expect(timeline.timeline[0].timeline.length).toBe(5); - expect(timeline.timeline[1].timeline.length).toBe(5); - expect(timeline.timeline[2].timeline.length).toBe(5); - }); - - it('should set correct n_back for each sub-timeline', () => { - const timeline = createMultiLevelNBackTimeline({ - n_backs: [1, 2, 3], - trials_per_level: 3 - }); - expect(timeline.timeline[0].timeline[0].data.n_back).toBe(1); - expect(timeline.timeline[1].timeline[0].data.n_back).toBe(2); - expect(timeline.timeline[2].timeline[0].data.n_back).toBe(3); - }); - - it('should randomize level order when randomize_levels is true', () => { - const timeline = createMultiLevelNBackTimeline({ - n_backs: [1, 2], - randomize_levels: true - }); - expect(timeline.randomize_order).toBe(true); - }); - }); - - describe('presetConfigurations', () => { - it('easy preset should have correct settings', () => { - const timeline = presetConfigurations.easy(); - expect(timeline.timeline[0].data.n_back).toBe(1); - expect(timeline.timeline[0].show_feedback_text).toBe(true); - expect(timeline.timeline[0].total_trials).toBe(15); - }); - - it('medium preset should have correct settings', () => { - const timeline = presetConfigurations.medium(); - expect(timeline.timeline[0].data.n_back).toBe(2); - expect(timeline.timeline[0].show_feedback_text).toBe(false); - expect(timeline.timeline[0].total_trials).toBe(20); - }); - - it('hard preset should have correct settings', () => { - const timeline = presetConfigurations.hard(); - expect(timeline.timeline[0].data.n_back).toBe(3); - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].cols).toBe(4); - expect(timeline.timeline[0].total_trials).toBe(25); - }); - - it('research preset should create multi-level timeline', () => { - const timeline = presetConfigurations.research(); - expect(Array.isArray(timeline.timeline)).toBe(true); - expect(timeline.timeline.length).toBe(3); // 1-back, 2-back, 3-back - }); - }); - - describe('parameter validation', () => { - it('should handle edge case parameters', () => { - const timeline = createTimeline({ - total_trials: 1, - target_percentage: 0, - n_back: 1 - }); - expect(timeline.timeline.length).toBe(1); - expect(timeline.timeline[0].is_target).toBe(false); // Can't be target with n_back=1 - }); - - it('should handle high target percentage correctly', () => { - const timeline = createTimeline({ - total_trials: 10, - target_percentage: 90, - n_back: 1 - }); - // Should not exceed maximum possible targets - const targets = timeline.timeline.filter(trial => trial.is_target); - expect(targets.length).toBeLessThanOrEqual(9); // Max with n_back=1 and 10 trials - }); - }); -}); \ No newline at end of file From 599b1ce67f2634e0a0b9a0b80d66eedccab421e5 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 17 Jun 2025 14:30:24 -0400 Subject: [PATCH 10/37] added text.ts and standardized text configuration. --- packages/spatial-nback/docs/spatial-nback.md | 2 +- packages/spatial-nback/examples/index.html | 9 +- packages/spatial-nback/package.json | 5 +- packages/spatial-nback/src/index.spec.ts | 13 +-- packages/spatial-nback/src/index.ts | 86 +++++++++++--------- packages/spatial-nback/src/text.ts | 38 +++++++++ 6 files changed, 97 insertions(+), 56 deletions(-) create mode 100644 packages/spatial-nback/src/text.ts diff --git a/packages/spatial-nback/docs/spatial-nback.md b/packages/spatial-nback/docs/spatial-nback.md index c4278322..3002f194 100644 --- a/packages/spatial-nback/docs/spatial-nback.md +++ b/packages/spatial-nback/docs/spatial-nback.md @@ -31,7 +31,7 @@ show_feedback_border | boolean | false | Whether to highlight the grid border fo show_feedback_no_click | boolean | false | Show feedback if no response is made feedback_wait_no_click | boolean | true | Wait for feedback even if no response cell_size | number | 150 | Size (px) of each grid cell -instructions_trial | string | "Click the button when the position matches the one from {n} trial(s) ago" | Instructions template for each trial +prompt | string | "Click the button when the position matches the one from {n} trial(s) ago" | Instructions template for each trial button_text | string | "MATCH" | Text for the match button stimulus_color | string | "#2196F3" | Color of the stimulus square correct_color | string | "#4CAF50" | Color for correct feedback diff --git a/packages/spatial-nback/examples/index.html b/packages/spatial-nback/examples/index.html index c3cddda3..e939484d 100644 --- a/packages/spatial-nback/examples/index.html +++ b/packages/spatial-nback/examples/index.html @@ -5,7 +5,6 @@ Spatial N-Back Task - @@ -21,7 +20,7 @@ } }); - const instructions_trial = jsPsychTimelineSpatialNback.utils.instructions_template; + const instructions = jsPsychTimelineSpatialNback.utils.createInstructions(); // Create a custom timeline const spatial1Back = jsPsychTimelineSpatialNback.createTimeline({ @@ -38,8 +37,8 @@ show_feedback_no_click : true, feedback_wait_no_click: true, cell_size: 150, - instructions_trial: "Click the button when the position matches the one from {n} trial(s) ago", - button_text: "MATCH", + prompt: "Click the button when the position matches the one from {n_back} trial{plural} ago", //overrides text.ts + button_text: "MATCH", //overrides text.ts stimulus_color: "#2196F3", correct_color: "#4CAF50", incorrect_color: "#F44336", @@ -57,7 +56,7 @@ // }); const spatial1BackTrials = spatial1Back.timeline; - jsPsych.run([instructions_trial, spatial1BackTrials]); + jsPsych.run([instructions, spatial1BackTrials]); \ No newline at end of file diff --git a/packages/spatial-nback/package.json b/packages/spatial-nback/package.json index ccd4732c..bffd43b7 100644 --- a/packages/spatial-nback/package.json +++ b/packages/spatial-nback/package.json @@ -33,9 +33,8 @@ "typescript": "^5.0.2" }, "dependencies": { - "@jspsych/plugin-html-button-response": "^2.1.0", - "@jspsych/plugin-html-keyboard-response": "^2.1.0", - "@jspsych-contrib/plugin-spatial-nback": "git@https://github.com/farhat60/jspsych-contrib/packages/plugin-spatial-nback", + "@jspsych-contrib/plugin-spatial-nback": "https://github.com/farhat60/jspsych-contrib/tree/main/packages", + "@jspsych/plugin-instructions": "^2.1.0", "jsPsych": "npm:jspsych@^8.2.1" } } diff --git a/packages/spatial-nback/src/index.spec.ts b/packages/spatial-nback/src/index.spec.ts index cc6d0bc0..a90f64b8 100644 --- a/packages/spatial-nback/src/index.spec.ts +++ b/packages/spatial-nback/src/index.spec.ts @@ -35,7 +35,7 @@ describe('createTimeline', () => { it('should include instructions when specified', () => { const timeline = createTimeline({ include_instructions: true }); expect(timeline.timeline).toHaveLength(2); - expect(timeline.timeline[0].choices).toEqual(['Continue']); + expect(timeline.timeline[0].show_clickable_nav).toBeTruthy(); }); it('should randomize trials when specified', () => { @@ -153,17 +153,10 @@ describe('presetConfigurations', () => { }); describe('timeline instruction customization', () => { - it('should update instructions for different n_backs', () => { - const timeline1 = createTimeline({ n_back: 1, include_instructions: true }); - const timeline2 = createTimeline({ n_back: 2, include_instructions: true }); - - expect(timeline1.timeline[0].stimulus).toContain('1 trial ago'); - expect(timeline2.timeline[0].stimulus).toContain('2 trials ago'); - }); - it('should replace placeholders in trial instructions', () => { + it('should replace placeholders in trial prompt', () => { const timeline = createTimeline({ - instructions_trial: "Level {n_back}, Trial {trial} of {total}", + prompt: "Level {n_back}, Trial {trial} of {total}", n_back: 2, total_trials: 5 }); diff --git a/packages/spatial-nback/src/index.ts b/packages/spatial-nback/src/index.ts index dff07c25..80e2445e 100644 --- a/packages/spatial-nback/src/index.ts +++ b/packages/spatial-nback/src/index.ts @@ -1,21 +1,37 @@ import { initJsPsych, JsPsych } from "jspsych" -import jsPsychPluginSpatialNback from "@jspsych/plugin-spatial-nback"; -import jsPsychHtmlKeyboardResponse from "@jspsych/plugin-html-keyboard-response"; -import jsPsychHtmlButtonResponse from "@jspsych/plugin-html-button-response"; +import jsPsychPluginSpatialNback from "@jspsych-contrib/plugin-spatial-nback"; +import jsPsychInstructions from "@jspsych/plugin-instructions"; +import { trial_text, instruction_pages } from "./text"; -const instructions_template = { - type: jsPsychHtmlButtonResponse, - stimulus: ` -
-

Spatial N-Back Task

-

In this task, you will see a grid with blue squares appearing in different positions.

-

Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.

-

Try to respond as quickly and accurately as possible.

-

Click the button below to begin the task.

-
- `, - choices: ['Continue'], -}; +function createInstructions(instructions = instruction_pages) { + const pages: string[] = []; + // Iterate through instruction texts and replace placeholders + for (const text of instruction_pages) { + pages.push(` +

${text.header || ""}

+

${text.header2 || ""}

+

${text.description || ""}

+

${text.task_explanation || ""}

+

${text.performance_note || ""}

+

${text.strategy_title || ""}

+

${text.strategy_intro || ""}

+
    + ${text.strategy_points?.map(point => `
  • ${point}
  • `).join('') || ""} +
+

${text.start_prompt || ""}

+ `); + } + + // Add the pages using instruction plugin + return { + type: jsPsychInstructions, + pages: pages, + button_label_next: "Continue", + button_label_previous: "Back", + show_clickable_nav: true + }; + +} // Generate stimulus sequence for n-back task function generateNBackSequence(total_trials: number, n_back: number, target_percentage: number, rows: number, cols: number) { @@ -79,16 +95,17 @@ export function createTimeline({ feedback_duration = 1000, show_feedback_text = false, show_feedback_border = false, - show_feedback_no_click = false, + show_feedback_no_click = false, feedback_wait_no_click = true, cell_size = 150, - instructions_trial = "Click the button when the position matches the one from {n} trial(s) ago", - button_text = "MATCH", + prompt = trial_text.prompt, + button_text = trial_text.button, stimulus_color = "#2196F3", correct_color = "#4CAF50", incorrect_color = "#F44336", include_instructions = false, randomize_trials = false, + instruction_texts = instruction_pages, }: { rows?: number, cols?: number, @@ -100,16 +117,17 @@ export function createTimeline({ feedback_duration?: number, show_feedback_text?: boolean, show_feedback_border?: boolean, - show_feedback_no_click ?: boolean, + show_feedback_no_click?: boolean, feedback_wait_no_click?: boolean, cell_size?: number, - instructions_trial?: string, + prompt?: string, button_text?: string, stimulus_color?: string, correct_color?: string, incorrect_color?: string, include_instructions?: boolean, randomize_trials?: boolean, + instruction_texts?: typeof instruction_pages, } = {}) { // Generate the sequence @@ -118,10 +136,11 @@ export function createTimeline({ // Create individual trial objects const trials = []; for (let i = 0; i < total_trials; i++) { - const trial_instructions = instructions_trial - .replace('{n_back}', n_back.toString()) - .replace('{trial}', (i + 1).toString()) - .replace('{total}', total_trials.toString()); + const trial_instructions = prompt + .replace(/{n_back}/g, n_back.toString()) + .replace(/{plural}/g, n_back > 1 ? 's' : '') + .replace(/{trial}/g, (i + 1).toString()) + .replace(/{total}/g, total_trials.toString()); trials.push({ type: jsPsychPluginSpatialNback, @@ -135,7 +154,7 @@ export function createTimeline({ feedback_duration: feedback_duration, show_feedback_text: show_feedback_text, show_feedback_border: show_feedback_border, - show_feedback_no_click : show_feedback_no_click , + show_feedback_no_click: show_feedback_no_click, feedback_wait_no_click: feedback_wait_no_click, cell_size: cell_size, instructions: trial_instructions, @@ -160,17 +179,10 @@ export function createTimeline({ // Return complete timeline with or without instructions if (include_instructions) { - // Update instructions to show current n-back level - const custom_instructions = { - ...instructions_template, - stimulus: instructions_template.stimulus.replace( - '1 trial ago', - `${n_back} trial${n_back > 1 ? 's' : ''} ago` - ) - }; + const instructions = createInstructions(instruction_texts); const nested_timeline = { - timeline: [custom_instructions, task_timeline] + timeline: [instructions, task_timeline] }; return nested_timeline; @@ -253,7 +265,7 @@ export const presetConfigurations = { }; // Export individual components for custom use -export { instructions_template, generateNBackSequence }; +export { createInstructions, generateNBackSequence }; // Export default timeline creator export default createTimeline; @@ -267,5 +279,5 @@ export const timelineUnits = { export const utils = { presetConfigurations, generateNBackSequence, - instructions_template + createInstructions } \ No newline at end of file diff --git a/packages/spatial-nback/src/text.ts b/packages/spatial-nback/src/text.ts new file mode 100644 index 00000000..bdad3096 --- /dev/null +++ b/packages/spatial-nback/src/text.ts @@ -0,0 +1,38 @@ +/* This file contains the configurable text used in the timeline. +replaceable holders: {n_back}, {plural}, {trial}, {total} */ +export const trial_text = { + button: "MATCH", + prompt: "Click the MATCH button if the current position matches the position from {n_back} trial{plural} ago. (trial {trial} of {total})", + correct: "Correct!", + incorrect: "Incorrect!", +} + +/* +* This is an array of page objects that have configurable texts for the instruction pages before the actual trials +* You can make as many pages as you would like, just add a new array and your text (e.g. ,{header2: "new page here"}) +* "page" objects are separated by commas, just as in any array. +* keys, in the order they show on a page: header, header2, description, task_explanation, performance_note, +strategy_title, strategy_intro, strategy_points (array of strings), start_prompt +* You can leave any keys or text options out if you do not want them in any specific page +*/ + +export const instruction_pages = [{ + header: "Spatial N-Back Task", //

+ header2: "Memory for Spatial Locations", //

+ description: "In this task, you will see a grid with blue squares appearing in different positions.", //

+ task_explanation: "Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.", //

+ performance_note: "Try to respond as quickly and accurately as possible.", //

+ + start_prompt: "Click the continue to move to the next page.", //

in bold and blue +}, //page two, you can add more pages if needed by creating more objects in the array +{ + strategy_title: "Strategy", + strategy_intro: "Before each trial, remember:", + strategy_points: [ + "Watch the grid carefully", + "Remember positions from ONE trial ago", + "Click MATCH only when positions match", + "Respond quickly but accurately" + ], + start_prompt: "Click continue to start the practice trial.", +}]; \ No newline at end of file From 3ac079b5c709b06603e70bca96bcd82935d501d3 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 17 Jun 2025 14:42:19 -0400 Subject: [PATCH 11/37] finalized spatial-nback timeline v1.0.0 --- package-lock.json | 1273 +++++++++++++++-- package.json | 3 +- .../src/index copy 2.ts | 299 ++++ packages/pattern-comparison-task/src/index.ts | 179 +-- packages/spatial-nback/package.json | 4 +- packages/spatial-nback/src/index.spec.ts | 2 - 6 files changed, 1567 insertions(+), 193 deletions(-) create mode 100644 packages/pattern-comparison-task/src/index copy 2.ts diff --git a/package-lock.json b/package-lock.json index 3b4a3e32..715592ca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,11 @@ "templates/cli" ], "dependencies": { + "@jspsych/test-utils": "^1.2.0", "chokidar": "^3.6.0", "gulp": "^5.0.0", "gulp-replace": "^1.1.4", - "jspsych": "^8.0.2", + "jspsych": "^8.2.1", "run-func": "^3.0.0" }, "devDependencies": { @@ -48,7 +49,6 @@ "version": "7.26.2", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -296,7 +296,6 @@ "version": "7.25.9", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", - "dev": true, "engines": { "node": ">=6.9.0" } @@ -2210,7 +2209,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, "dependencies": { "jest-get-type": "^29.6.3" }, @@ -2313,7 +2311,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, "dependencies": { "@sinclair/typebox": "^0.27.8" }, @@ -2411,7 +2408,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", @@ -2428,7 +2424,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -2488,6 +2483,18 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jspsych-contrib/plugin-spatial-nback": { + "name": "@jspsych/jspsych-contrib", + "resolved": "git+ssh://git@github.com/farhat60/jspsych-contrib.git#814d4f3cfb134bbcc4357b7c25b4221ffcb1674c", + "workspaces": [ + "packages/*", + "templates/cli" + ], + "dependencies": { + "gulp": "^5.0.0", + "gulp-replace": "^1.1.4" + } + }, "node_modules/@jspsych-timelines/arrow-flanker": { "resolved": "packages/arrow-flanker", "link": true @@ -2500,10 +2507,22 @@ "resolved": "packages/false-memory", "link": true }, + "node_modules/@jspsych-timelines/pattern-comparison-task": { + "resolved": "packages/pattern-comparison-task", + "link": true + }, + "node_modules/@jspsych-timelines/pattern-comparison-test": { + "resolved": "packages/pattern-comparison-test", + "link": true + }, "node_modules/@jspsych-timelines/spatial-cueing": { "resolved": "packages/spatial-cueing", "link": true }, + "node_modules/@jspsych-timelines/spatial-nback": { + "resolved": "packages/spatial-nback", + "link": true + }, "node_modules/@jspsych/config": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@jspsych/config/-/config-3.2.2.tgz", @@ -2573,19 +2592,19 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true }, - "node_modules/@jspsych/plugin-html-button-response": { + "node_modules/@jspsych/plugin-html-keyboard-response": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-button-response/-/plugin-html-button-response-2.1.0.tgz", - "integrity": "sha512-AhJPePA8s/ount+QYtqfgMtEkz7g+j+bnhQ9XqxfsnK2f5MdnlakYtpjxW9YgFx9y0Gs7yb7OZk1+omcKlzaEQ==", + "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", + "integrity": "sha512-NmtYU9798mEJ6CLQREp6bNHo1e+Ca5zwAznBlREmpUMdOrqBhTLrF5T/Gg4t2MJHm4lFN/fwHJtt0g5vMAoYFg==", "license": "MIT", "peerDependencies": { "jspsych": ">=7.1.0" } }, - "node_modules/@jspsych/plugin-html-keyboard-response": { + "node_modules/@jspsych/plugin-instructions": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", - "integrity": "sha512-NmtYU9798mEJ6CLQREp6bNHo1e+Ca5zwAznBlREmpUMdOrqBhTLrF5T/Gg4t2MJHm4lFN/fwHJtt0g5vMAoYFg==", + "resolved": "https://registry.npmjs.org/@jspsych/plugin-instructions/-/plugin-instructions-2.1.0.tgz", + "integrity": "sha512-jg3bepI9axwenGMNGunmcnnsXn4V2xMhVrzfHUGkA7AP/TQLdS8aeNqwhq3SVy6EXPX8X+r/sr2GKHrXeViRFA==", "license": "MIT", "peerDependencies": { "jspsych": ">=7.1.0" @@ -2599,6 +2618,16 @@ "jspsych": ">=7.1.0" } }, + "node_modules/@jspsych/test-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@jspsych/test-utils/-/test-utils-1.2.0.tgz", + "integrity": "sha512-3j0n0k6/DLSIz91ngqV/SXfXONnpaBC/McCrXysq6kMS08utzTEX56xKNzEmfY37bQKCijJsBreF6oH9Zss60w==", + "license": "MIT", + "peerDependencies": { + "@types/jest": "*", + "jspsych": ">=7.0.0" + } + }, "node_modules/@manypkg/find-root": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@manypkg/find-root/-/find-root-1.1.0.tgz", @@ -3071,8 +3100,7 @@ "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==" }, "node_modules/@sinonjs/commons": { "version": "3.0.1", @@ -3198,14 +3226,12 @@ "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, "dependencies": { "@types/istanbul-lib-coverage": "*" } @@ -3214,7 +3240,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, "dependencies": { "@types/istanbul-lib-report": "*" } @@ -3223,7 +3248,6 @@ "version": "29.5.12", "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", - "dev": true, "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" @@ -3280,8 +3304,7 @@ "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==" }, "node_modules/@types/streamx": { "version": "2.9.5", @@ -3344,7 +3367,6 @@ "version": "17.0.33", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, "dependencies": { "@types/yargs-parser": "*" } @@ -3352,8 +3374,7 @@ "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==" }, "node_modules/abab": { "version": "2.0.6", @@ -4220,7 +4241,6 @@ "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, "funding": [ { "type": "github", @@ -4845,7 +4865,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -5171,7 +5190,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", @@ -7272,7 +7290,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", @@ -7287,7 +7304,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7391,7 +7407,6 @@ "version": "29.6.3", "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } @@ -7438,7 +7453,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", @@ -7453,7 +7467,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7469,7 +7482,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", @@ -7489,7 +7501,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7753,7 +7764,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", @@ -7770,7 +7780,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -7904,8 +7913,7 @@ "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "node_modules/js-yaml": { "version": "3.14.1", @@ -9148,8 +9156,7 @@ "node_modules/picocolors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", - "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", - "dev": true + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -9275,7 +9282,6 @@ "version": "29.7.0", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", @@ -9289,7 +9295,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "engines": { "node": ">=10" }, @@ -9393,8 +9398,7 @@ "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" }, "node_modules/read-yaml-file": { "version": "1.1.0", @@ -9963,7 +9967,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, "engines": { "node": ">=8" } @@ -10016,10 +10019,6 @@ "node": ">= 10.13.0" } }, - "node_modules/spatial-nback-task": { - "resolved": "packages/spatial-nback-task", - "link": true - }, "node_modules/spawndamnit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/spawndamnit/-/spawndamnit-2.0.0.tgz", @@ -10100,7 +10099,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -10112,7 +10110,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, "engines": { "node": ">=8" } @@ -11671,34 +11668,1110 @@ "jspsych": "^8.0.1" } }, - "packages/spatial-cueing": { - "name": "@jspsych-timelines/spatial-cueing", - "version": "0.2.0", - "license": "MIT", - "devDependencies": { - "tsup": "^8.2.4", - "typescript": "^5.5.4" - }, - "peerDependencies": { - "jspsych": "^8.0.1" - } - }, - "packages/spatial-nback-task": { + "packages/pattern-comparison-task": { + "name": "@jspsych-timelines/pattern-comparison-task", "version": "0.0.1", "license": "MIT", - "dependencies": { - "@jspsych/plugin-html-button-response": "^2.1.0", - "@jspsych/plugin-html-keyboard-response": "^2.1.0", - "jsPsych": "npm:jspsych@^8.2.1" - }, "devDependencies": { "@jspsych/config": "^3.2.2", - "jspsych": "^8.2.1", + "jspsych": "^8.0.0", "tsup": "^6.7.0", "typescript": "^5.0.2" } }, - "packages/spatial-nback-task/node_modules/@esbuild/android-arm": { + "packages/pattern-comparison-task/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-task/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "packages/pattern-comparison-task/node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "packages/pattern-comparison-task/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "packages/pattern-comparison-task/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "packages/pattern-comparison-task/node_modules/tsup": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", + "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^4.0.0", + "cac": "^6.7.12", + "chokidar": "^3.5.1", + "debug": "^4.3.1", + "esbuild": "^0.17.6", + "execa": "^5.0.0", + "globby": "^11.0.3", + "joycon": "^3.0.1", + "postcss-load-config": "^3.0.1", + "resolve-from": "^5.0.0", + "rollup": "^3.2.5", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.20.3", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "packages/pattern-comparison-task/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "packages/pattern-comparison-task/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "packages/pattern-comparison-test": { + "name": "@jspsych-timelines/pattern-comparison-test", + "version": "0.0.1", + "license": "MIT", + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.0.0", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/android-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", + "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/android-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", + "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/android-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", + "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/darwin-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", + "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/darwin-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", + "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/freebsd-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", + "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/freebsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", + "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-arm": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", + "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", + "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", + "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-loong64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", + "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-mips64el": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", + "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-ppc64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", + "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-riscv64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", + "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-s390x": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", + "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/linux-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", + "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/netbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", + "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/openbsd-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", + "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/sunos-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", + "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/win32-arm64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", + "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/win32-ia32": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", + "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/@esbuild/win32-x64": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", + "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "packages/pattern-comparison-test/node_modules/esbuild": { + "version": "0.17.19", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", + "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.17.19", + "@esbuild/android-arm64": "0.17.19", + "@esbuild/android-x64": "0.17.19", + "@esbuild/darwin-arm64": "0.17.19", + "@esbuild/darwin-x64": "0.17.19", + "@esbuild/freebsd-arm64": "0.17.19", + "@esbuild/freebsd-x64": "0.17.19", + "@esbuild/linux-arm": "0.17.19", + "@esbuild/linux-arm64": "0.17.19", + "@esbuild/linux-ia32": "0.17.19", + "@esbuild/linux-loong64": "0.17.19", + "@esbuild/linux-mips64el": "0.17.19", + "@esbuild/linux-ppc64": "0.17.19", + "@esbuild/linux-riscv64": "0.17.19", + "@esbuild/linux-s390x": "0.17.19", + "@esbuild/linux-x64": "0.17.19", + "@esbuild/netbsd-x64": "0.17.19", + "@esbuild/openbsd-x64": "0.17.19", + "@esbuild/sunos-x64": "0.17.19", + "@esbuild/win32-arm64": "0.17.19", + "@esbuild/win32-ia32": "0.17.19", + "@esbuild/win32-x64": "0.17.19" + } + }, + "packages/pattern-comparison-test/node_modules/rollup": { + "version": "3.29.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", + "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "packages/pattern-comparison-test/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "packages/pattern-comparison-test/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "packages/pattern-comparison-test/node_modules/tsup": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", + "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bundle-require": "^4.0.0", + "cac": "^6.7.12", + "chokidar": "^3.5.1", + "debug": "^4.3.1", + "esbuild": "^0.17.6", + "execa": "^5.0.0", + "globby": "^11.0.3", + "joycon": "^3.0.1", + "postcss-load-config": "^3.0.1", + "resolve-from": "^5.0.0", + "rollup": "^3.2.5", + "source-map": "0.8.0-beta.0", + "sucrase": "^3.20.3", + "tree-kill": "^1.2.2" + }, + "bin": { + "tsup": "dist/cli-default.js", + "tsup-node": "dist/cli-node.js" + }, + "engines": { + "node": ">=14.18" + }, + "peerDependencies": { + "@swc/core": "^1", + "postcss": "^8.4.12", + "typescript": ">=4.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "postcss": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "packages/pattern-comparison-test/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "packages/pattern-comparison-test/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "packages/spatial-cueing": { + "name": "@jspsych-timelines/spatial-cueing", + "version": "0.2.0", + "license": "MIT", + "devDependencies": { + "tsup": "^8.2.4", + "typescript": "^5.5.4" + }, + "peerDependencies": { + "jspsych": "^8.0.1" + } + }, + "packages/spatial-nback": { + "name": "@jspsych-timelines/spatial-nback", + "version": "0.0.2", + "license": "MIT", + "dependencies": { + "@jspsych-contrib/plugin-spatial-nback": "https://github.com/farhat60/jspsych-contrib/tree/main/", + "@jspsych/plugin-instructions": "^2.1.0", + "jsPsych": "npm:jspsych@^8.2.1" + }, + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + }, + "packages/spatial-nback-task": { + "version": "0.0.1", + "extraneous": true, + "license": "MIT", + "dependencies": { + "@jspsych/plugin-html-button-response": "^2.1.0", + "@jspsych/plugin-html-keyboard-response": "^2.1.0", + "jsPsych": "npm:jspsych@^8.2.1" + }, + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + }, + "packages/spatial-nback/git@https:/github.com/farhat60/jspsych-contrib/packages/plugin-spatial-nback": { + "extraneous": true + }, + "packages/spatial-nback/node_modules/@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", @@ -11715,7 +12788,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/android-arm64": { + "packages/spatial-nback/node_modules/@esbuild/android-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", @@ -11732,7 +12805,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/android-x64": { + "packages/spatial-nback/node_modules/@esbuild/android-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", @@ -11749,7 +12822,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/darwin-arm64": { + "packages/spatial-nback/node_modules/@esbuild/darwin-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", @@ -11766,7 +12839,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/darwin-x64": { + "packages/spatial-nback/node_modules/@esbuild/darwin-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", @@ -11783,7 +12856,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/freebsd-arm64": { + "packages/spatial-nback/node_modules/@esbuild/freebsd-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", @@ -11800,7 +12873,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/freebsd-x64": { + "packages/spatial-nback/node_modules/@esbuild/freebsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", @@ -11817,7 +12890,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-arm": { + "packages/spatial-nback/node_modules/@esbuild/linux-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", @@ -11834,7 +12907,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-arm64": { + "packages/spatial-nback/node_modules/@esbuild/linux-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", @@ -11851,7 +12924,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-ia32": { + "packages/spatial-nback/node_modules/@esbuild/linux-ia32": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", @@ -11868,7 +12941,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-loong64": { + "packages/spatial-nback/node_modules/@esbuild/linux-loong64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", @@ -11885,7 +12958,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-mips64el": { + "packages/spatial-nback/node_modules/@esbuild/linux-mips64el": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", @@ -11902,7 +12975,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-ppc64": { + "packages/spatial-nback/node_modules/@esbuild/linux-ppc64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", @@ -11919,7 +12992,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-riscv64": { + "packages/spatial-nback/node_modules/@esbuild/linux-riscv64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", @@ -11936,7 +13009,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-s390x": { + "packages/spatial-nback/node_modules/@esbuild/linux-s390x": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", @@ -11953,7 +13026,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/linux-x64": { + "packages/spatial-nback/node_modules/@esbuild/linux-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", @@ -11970,7 +13043,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/netbsd-x64": { + "packages/spatial-nback/node_modules/@esbuild/netbsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", @@ -11987,7 +13060,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/openbsd-x64": { + "packages/spatial-nback/node_modules/@esbuild/openbsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", @@ -12004,7 +13077,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/sunos-x64": { + "packages/spatial-nback/node_modules/@esbuild/sunos-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", @@ -12021,7 +13094,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/win32-arm64": { + "packages/spatial-nback/node_modules/@esbuild/win32-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", @@ -12038,7 +13111,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/win32-ia32": { + "packages/spatial-nback/node_modules/@esbuild/win32-ia32": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", @@ -12055,7 +13128,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/@esbuild/win32-x64": { + "packages/spatial-nback/node_modules/@esbuild/win32-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", @@ -12072,7 +13145,7 @@ "node": ">=12" } }, - "packages/spatial-nback-task/node_modules/esbuild": { + "packages/spatial-nback/node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", @@ -12110,7 +13183,7 @@ "@esbuild/win32-x64": "0.17.19" } }, - "packages/spatial-nback-task/node_modules/rollup": { + "packages/spatial-nback/node_modules/rollup": { "version": "3.29.5", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", @@ -12127,7 +13200,7 @@ "fsevents": "~2.3.2" } }, - "packages/spatial-nback-task/node_modules/source-map": { + "packages/spatial-nback/node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", @@ -12140,7 +13213,7 @@ "node": ">= 8" } }, - "packages/spatial-nback-task/node_modules/tr46": { + "packages/spatial-nback/node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", @@ -12150,7 +13223,7 @@ "punycode": "^2.1.0" } }, - "packages/spatial-nback-task/node_modules/tsup": { + "packages/spatial-nback/node_modules/tsup": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", @@ -12196,14 +13269,14 @@ } } }, - "packages/spatial-nback-task/node_modules/webidl-conversions": { + "packages/spatial-nback/node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true, "license": "BSD-2-Clause" }, - "packages/spatial-nback-task/node_modules/whatwg-url": { + "packages/spatial-nback/node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", diff --git a/package.json b/package.json index 1429c510..f1bed32b 100644 --- a/package.json +++ b/package.json @@ -50,10 +50,11 @@ ] }, "dependencies": { + "@jspsych/test-utils": "^1.2.0", "chokidar": "^3.6.0", "gulp": "^5.0.0", "gulp-replace": "^1.1.4", - "jspsych": "^8.0.2", + "jspsych": "^8.2.1", "run-func": "^3.0.0" } } diff --git a/packages/pattern-comparison-task/src/index copy 2.ts b/packages/pattern-comparison-task/src/index copy 2.ts new file mode 100644 index 00000000..72ec6605 --- /dev/null +++ b/packages/pattern-comparison-task/src/index copy 2.ts @@ -0,0 +1,299 @@ +import { JsPsych, TrialType } from "jspsych" +import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" + +interface PatternComparisonConfig { + /** Array of SVG strings or URLs to use as patterns */ + patterns?: string[] + /** Number of trials to generate */ + numTrials?: number + /** Instructions text to display above each trial */ + instructions?: string + /** Enable text-to-speech for instructions */ + enableTTS?: boolean + /** Text for the "same" button */ + sameButtonText?: string + /** Text for the "different" button */ + differentButtonText?: string + /** Maximum time allowed per trial (in ms) */ + trialTimeout?: number + /** Inter-trial interval (in ms) */ + interTrialInterval?: number + /** Number of colored patterns to generate */ + numPatterns?: number +} + +// Base SVG shapes without colors +const svgShapes = [ + // Lightbulb + ``, + + // Camera + ``, + + // Key + ``, + + // Book + ``, + + // Anchor + ``, + + // Envelope (Mail) + ``, + + // Gift Box + ``, + + // Flag + ``, +]; + +// Color palette for random selection +const colors = [ + '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', + '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9', + '#F8C471', '#82E0AA', '#F1948A', '#85C1E9', '#D5A6BD', + '#A569BD', '#5DADE2', '#58D68D', '#F4D03F', '#EB984E' +]; + +function generatePatterns(numPatterns: number = 20): string[] { + const patterns = []; + + for (let i = 0; i < numPatterns; i++) { + // Select random shape + const shapeIndex = Math.floor(Math.random() * svgShapes.length); + const baseShape = svgShapes[shapeIndex]; + + // Select random colors + const fillColor = colors[Math.floor(Math.random() * colors.length)]; + const strokeColor = colors[Math.floor(Math.random() * colors.length)]; + + // Replace placeholders with actual colors + const coloredPattern = baseShape + .replace(/FILL_COLOR/g, fillColor) + .replace(/STROKE_COLOR/g, strokeColor); + + patterns.push(coloredPattern); + } + + return patterns; +} + +function generateTrials(config: PatternComparisonConfig) { + // Use patterns from config or generate colored patterns + const patterns = config.patterns || generatePatterns(config.numPatterns || 20); + const numTrials = config.numTrials || 20; + const trials = []; + + for (let i = 0; i < numTrials; i++) { + // Randomly decide if patterns should be same or different + const isSame = Math.random() < 0.5; + + // Select first pattern randomly + const pattern1Index = Math.floor(Math.random() * patterns.length); + const pattern1 = patterns[pattern1Index]; + + // Select second pattern based on isSame + let pattern2; + if (isSame) { + pattern2 = pattern1; + } else { + let pattern2Index; + do { + pattern2Index = Math.floor(Math.random() * patterns.length); + } while (pattern2Index === pattern1Index); + pattern2 = patterns[pattern2Index]; + } + + trials.push({ + pattern1, + pattern2, + correctAnswer: isSame ? 0 : 1 // 0 for same, 1 for different + }); + } + + return trials; +} + +export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig = {}) { + const { + instructions = "Are these two patterns the same?", + enableTTS = false, + sameButtonText = "Same", + differentButtonText = "Different", + trialTimeout = 10000, + interTrialInterval = 500 + } = config + + const trials = generateTrials(config) + const timeline = [] + + // Instructions screen + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +

+

Pattern Comparison Task

+

You will see two patterns side by side. Your task is to decide whether they are the same or different.

+

Respond as quickly and accurately as possible.

+

Click the button below to start.

+
+ `, + choices: ['Start'], + margin_horizontal: '10px', + margin_vertical: '10px' + }) + + // Create trial timeline + trials.forEach((trial, index) => { + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` + +
+
${instructions}
+
+
${trial.pattern1}
+
${trial.pattern2}
+
+
+ `, + choices: [sameButtonText, differentButtonText], + margin_horizontal: '20px', + margin_vertical: '15px', + button_html: function(choice, choice_index) { + return ``; + }, + trial_duration: trialTimeout, + data: { + task: 'pattern-comparison', + trial_number: index + 1, + correct_answer: trial.correctAnswer, + pattern1: trial.pattern1, + pattern2: trial.pattern2, + is_same: trial.correctAnswer === 0 + }, + on_finish: function(data: any) { + data.correct = data.response === data.correct_answer + data.reaction_time = data.rt + }, + on_start: function() { + if (enableTTS && 'speechSynthesis' in window) { + const utterance = new SpeechSynthesisUtterance(instructions) + speechSynthesis.speak(utterance) + } + } + }) + + // Inter-trial interval + if (index < trials.length - 1) { + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: '
+
', + choices: [], + trial_duration: interTrialInterval + }) + } + }) + + // End screen + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

Task Complete!

+

Thank you for participating in the pattern comparison task.

+
+ `, + choices: ['Continue'] + }) + + return timeline +} + +export const timelineUnits = { + instructions: "Instructions for the pattern comparison task", + trial: "Single pattern comparison trial", + interTrialInterval: "Fixation cross between trials", + endScreen: "Task completion screen" +} + +export const utils = { + generateTrials, + generatePatterns, + svgShapes, // Export the base shapes + colors, // Export the color palette + + /** Calculate accuracy and reaction time statistics */ + calculatePerformance: function(data: any[]) { + const trialData = data.filter(d => d.task === 'pattern-comparison') + const correct = trialData.filter(d => d.correct).length + const total = trialData.length + const accuracy = total > 0 ? (correct / total) * 100 : 0 + + const validRTs = trialData.filter(d => d.correct && d.rt !== null).map(d => d.rt) + const meanRT = validRTs.length > 0 ? validRTs.reduce((a, b) => a + b, 0) / validRTs.length : null + + return { + accuracy, + meanReactionTime: meanRT, + totalTrials: total, + correctTrials: correct + } + } +} + +// Default export for convenience +export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/index.ts b/packages/pattern-comparison-task/src/index.ts index 72ec6605..2a7d6392 100644 --- a/packages/pattern-comparison-task/src/index.ts +++ b/packages/pattern-comparison-task/src/index.ts @@ -1,9 +1,13 @@ import { JsPsych, TrialType } from "jspsych" import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" +interface TestCategory { + [testName: string]: [string, string] // [original_svg, edited_svg] +} + interface PatternComparisonConfig { - /** Array of SVG strings or URLs to use as patterns */ - patterns?: string[] + /** Array of three test categories, each containing test pairs */ + testCategories?: TestCategory[] /** Number of trials to generate */ numTrials?: number /** Instructions text to display above each trial */ @@ -18,98 +22,73 @@ interface PatternComparisonConfig { trialTimeout?: number /** Inter-trial interval (in ms) */ interTrialInterval?: number - /** Number of colored patterns to generate */ - numPatterns?: number } -// Base SVG shapes without colors -const svgShapes = [ - // Lightbulb - ``, - - // Camera - ``, - - // Key - ``, - - // Book - ``, - - // Anchor - ``, - - // Envelope (Mail) - ``, - - // Gift Box - ``, - - // Flag - ``, -]; - -// Color palette for random selection -const colors = [ - '#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', - '#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9', - '#F8C471', '#82E0AA', '#F1948A', '#85C1E9', '#D5A6BD', - '#A569BD', '#5DADE2', '#58D68D', '#F4D03F', '#EB984E' -]; - -function generatePatterns(numPatterns: number = 20): string[] { - const patterns = []; - - for (let i = 0; i < numPatterns; i++) { - // Select random shape - const shapeIndex = Math.floor(Math.random() * svgShapes.length); - const baseShape = svgShapes[shapeIndex]; - - // Select random colors - const fillColor = colors[Math.floor(Math.random() * colors.length)]; - const strokeColor = colors[Math.floor(Math.random() * colors.length)]; - - // Replace placeholders with actual colors - const coloredPattern = baseShape - .replace(/FILL_COLOR/g, fillColor) - .replace(/STROKE_COLOR/g, strokeColor); - - patterns.push(coloredPattern); +// Default test categories based on the PMC article methodology +const defaultTestCategories: TestCategory[] = [ + // Category 1: Simple geometric patterns + { + "circle_pattern": [ + ``, + `` + ], + "square_pattern": [ + ``, + `` + ] + }, + // Category 2: Complex geometric patterns + { + "triangle_composition": [ + ``, + `` + ], + "hexagon_pattern": [ + ``, + `` + ] + }, + // Category 3: Abstract/irregular patterns + { + "irregular_shape": [ + ``, + `` + ], + "organic_form": [ + ``, + `` + ] } - - return patterns; -} +]; function generateTrials(config: PatternComparisonConfig) { - // Use patterns from config or generate colored patterns - const patterns = config.patterns || generatePatterns(config.numPatterns || 20); + const testCategories = config.testCategories || defaultTestCategories; const numTrials = config.numTrials || 20; const trials = []; for (let i = 0; i < numTrials; i++) { + // Randomly select a category + const categoryIndex = Math.floor(Math.random() * testCategories.length); + const selectedCategory = testCategories[categoryIndex]; + + // Randomly select a test within the category + const testNames = Object.keys(selectedCategory); + const testName = testNames[Math.floor(Math.random() * testNames.length)]; + const [originalSvg, editedSvg] = selectedCategory[testName]; + // Randomly decide if patterns should be same or different const isSame = Math.random() < 0.5; - // Select first pattern randomly - const pattern1Index = Math.floor(Math.random() * patterns.length); - const pattern1 = patterns[pattern1Index]; - - // Select second pattern based on isSame - let pattern2; - if (isSame) { - pattern2 = pattern1; - } else { - let pattern2Index; - do { - pattern2Index = Math.floor(Math.random() * patterns.length); - } while (pattern2Index === pattern1Index); - pattern2 = patterns[pattern2Index]; - } + const pattern1 = originalSvg; + const pattern2 = isSame ? originalSvg : editedSvg; trials.push({ pattern1, pattern2, - correctAnswer: isSame ? 0 : 1 // 0 for same, 1 for different + correctAnswer: isSame ? 0 : 1, // 0 for same, 1 for different + categoryIndex, + testName, + isSame }); } @@ -136,6 +115,7 @@ export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig

Pattern Comparison Task

You will see two patterns side by side. Your task is to decide whether they are the same or different.

+

Look carefully at both patterns and compare them closely.

Respond as quickly and accurately as possible.

Click the button below to start.

@@ -221,9 +201,11 @@ export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig task: 'pattern-comparison', trial_number: index + 1, correct_answer: trial.correctAnswer, + category_index: trial.categoryIndex, + test_name: trial.testName, + is_same: trial.isSame, pattern1: trial.pattern1, - pattern2: trial.pattern2, - is_same: trial.correctAnswer === 0 + pattern2: trial.pattern2 }, on_finish: function(data: any) { data.correct = data.response === data.correct_answer @@ -272,11 +254,9 @@ export const timelineUnits = { export const utils = { generateTrials, - generatePatterns, - svgShapes, // Export the base shapes - colors, // Export the color palette + defaultTestCategories, - /** Calculate accuracy and reaction time statistics */ + /** Calculate accuracy and reaction time statistics by category */ calculatePerformance: function(data: any[]) { const trialData = data.filter(d => d.task === 'pattern-comparison') const correct = trialData.filter(d => d.correct).length @@ -286,11 +266,34 @@ export const utils = { const validRTs = trialData.filter(d => d.correct && d.rt !== null).map(d => d.rt) const meanRT = validRTs.length > 0 ? validRTs.reduce((a, b) => a + b, 0) / validRTs.length : null + // Calculate performance by category + const categoryPerformance = [0, 1, 2].map(categoryIndex => { + const categoryTrials = trialData.filter(d => d.category_index === categoryIndex) + const categoryCorrect = categoryTrials.filter(d => d.correct).length + const categoryTotal = categoryTrials.length + const categoryAccuracy = categoryTotal > 0 ? (categoryCorrect / categoryTotal) * 100 : 0 + + const categoryValidRTs = categoryTrials.filter(d => d.correct && d.rt !== null).map(d => d.rt) + const categoryMeanRT = categoryValidRTs.length > 0 ? + categoryValidRTs.reduce((a, b) => a + b, 0) / categoryValidRTs.length : null + + return { + categoryIndex, + accuracy: categoryAccuracy, + meanReactionTime: categoryMeanRT, + totalTrials: categoryTotal, + correctTrials: categoryCorrect + } + }) + return { - accuracy, - meanReactionTime: meanRT, - totalTrials: total, - correctTrials: correct + overall: { + accuracy, + meanReactionTime: meanRT, + totalTrials: total, + correctTrials: correct + }, + byCategory: categoryPerformance } } } diff --git a/packages/spatial-nback/package.json b/packages/spatial-nback/package.json index bffd43b7..523d604d 100644 --- a/packages/spatial-nback/package.json +++ b/packages/spatial-nback/package.json @@ -7,7 +7,7 @@ "types": "dist/index.d.ts", "unpkg": "dist/index.browser.min.js", "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpatialNback" + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpatialNback --external @jspsych-contrib/plugin-spatial-nback" }, "repository": { "type": "git", @@ -33,7 +33,7 @@ "typescript": "^5.0.2" }, "dependencies": { - "@jspsych-contrib/plugin-spatial-nback": "https://github.com/farhat60/jspsych-contrib/tree/main/packages", + "@jspsych-contrib/plugin-spatial-nback": "https://github.com/farhat60/jspsych-contrib/tree/main/", "@jspsych/plugin-instructions": "^2.1.0", "jsPsych": "npm:jspsych@^8.2.1" } diff --git a/packages/spatial-nback/src/index.spec.ts b/packages/spatial-nback/src/index.spec.ts index a90f64b8..5e1e02b2 100644 --- a/packages/spatial-nback/src/index.spec.ts +++ b/packages/spatial-nback/src/index.spec.ts @@ -1,5 +1,3 @@ -import { startTimeline } from "@jspsych/test-utils"; -import jsPsychPluginSpatialNback from "@jspsych-contrib/plugin-spatial-nback"; import { createTimeline, createPracticeTimeline, createMultiLevelNBackTimeline, presetConfigurations } from "./index"; /* Test suite for Spatial N-Back Timeline From 53d13c12d4b0bb2dbfb56e940a3403e73165e830 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Wed, 25 Jun 2025 11:44:15 -0400 Subject: [PATCH 12/37] Revamped CSS to near perfection ngl --- packages/speeded-matching/README.md | 50 ++ .../speeded-matching/docs/speeded-matching.md | 51 ++ packages/speeded-matching/examples/index.html | 51 ++ packages/speeded-matching/jest.config.js | 1 + packages/speeded-matching/package.json | 35 ++ packages/speeded-matching/src/index.ts | 530 ++++++++++++++++++ packages/speeded-matching/src/styles.css | 266 +++++++++ packages/speeded-matching/src/test-items.ts | 116 ++++ packages/speeded-matching/src/text.ts | 78 +++ packages/speeded-matching/tsconfig.json | 10 + 10 files changed, 1188 insertions(+) create mode 100644 packages/speeded-matching/README.md create mode 100644 packages/speeded-matching/docs/speeded-matching.md create mode 100644 packages/speeded-matching/examples/index.html create mode 100644 packages/speeded-matching/jest.config.js create mode 100644 packages/speeded-matching/package.json create mode 100644 packages/speeded-matching/src/index.ts create mode 100644 packages/speeded-matching/src/styles.css create mode 100644 packages/speeded-matching/src/test-items.ts create mode 100644 packages/speeded-matching/src/text.ts create mode 100644 packages/speeded-matching/tsconfig.json diff --git a/packages/speeded-matching/README.md b/packages/speeded-matching/README.md new file mode 100644 index 00000000..e7efb158 --- /dev/null +++ b/packages/speeded-matching/README.md @@ -0,0 +1,50 @@ +# speeded-matching + +## Overview + +An assessment of processing speed. Participants are asked to identify which of four pictures matches the target picture at the top of the screen. + +## Loading + +### In browser + +```html + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/speeded-matching/jest.config.js b/packages/speeded-matching/jest.config.js new file mode 100644 index 00000000..bd1aacef --- /dev/null +++ b/packages/speeded-matching/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/speeded-matching/package.json b/packages/speeded-matching/package.json new file mode 100644 index 00000000..d7e8490b --- /dev/null +++ b/packages/speeded-matching/package.json @@ -0,0 +1,35 @@ +{ + "name": "@jspsych-timelines/speeded-matching", + "version": "0.0.1", + "description": "An assessment of processing speed. Participants are asked to identify which of four pictures matches the target picture at the top of the screen.", + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.ts", + "unpkg": "dist/index.browser.min.js", + "scripts": { + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpeededMatching" + }, + "repository": { + "type": "git", + "url": "https://github.com/farhat60/jspsych-timelines/", + "directory": "packages" + }, + "keywords": [ + "jsPsych" + ], + "author": { + "name": "Hunter Farhat", + "url": "github.com/farhat60" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/farhat60/jspsych-timelines//issues" + }, + "homepage": "https://github.com/jspsych/jspsych-timelines/packages/speeded-matching/README.md", + "devDependencies": { + "jspsych": "^8.0.0", + "@jspsych/config": "^3.2.2", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + } \ No newline at end of file diff --git a/packages/speeded-matching/src/index.ts b/packages/speeded-matching/src/index.ts new file mode 100644 index 00000000..dadf376a --- /dev/null +++ b/packages/speeded-matching/src/index.ts @@ -0,0 +1,530 @@ +import { JsPsych, TrialType } from "jspsych" +import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" +import { test_items } from "./test-items" +import { trial_text, instruction_pages } from "./text" + +interface SpeedMatchingConfig { + /** Array of test items (animal SVGs) to use as stimuli */ + test_items?: string[] + /** Number of trials to generate */ + num_trials?: number + /** Number of choice options per trial (default 4) */ + num_choices?: number + /** Enable text-to-speech for instructions and prompts */ + enable_tts?: boolean + /** Maximum time allowed per trial (in ms) */ + trial_timeout?: number + /** Inter-trial interval (in ms) */ + inter_trial_interval?: number + /** Show instruction pages before the task */ + show_instructions?: boolean + /** Show practice round before main task */ + show_practice?: boolean + /** Custom instruction texts */ + instruction_texts?: typeof instruction_pages +} + +/** + * Function to provide text-to-speech functionality + * Researchers can modify speech settings like rate and volume + */ +function speakText(text: string) { + if ('speechSynthesis' in window) { + // Stop any ongoing speech + speechSynthesis.cancel(); + + // Wait a brief moment for cancel to take effect + setTimeout(() => { + // Create and speak the utterance + const utterance = new SpeechSynthesisUtterance(text); + utterance.rate = 0.8; // Slightly slower for clarity + utterance.volume = 0.8; + utterance.voice = speechSynthesis.getVoices()[0] || null; // Use first available voice + speechSynthesis.speak(utterance); + }, 100); + } +} + +/** + * Function to get a random selection of test items for creating choice sets + * This ensures variety in the stimuli presented to participants + */ +function getRandomTestItems(items: string[], count: number = 4): string[] { + const shuffled = [...items].sort(() => 0.5 - Math.random()); + return shuffled.slice(0, Math.min(count, items.length)); +} + +/** + * Function to create a trial set with one target and distractors + * target_index: index of the item to use as target + * num_choices: total number of choices to present (default 4) + * Returns an object with target and choices array + */ +function createTrialSet(items: string[], target_index: number = 0, num_choices: number = 4) { + if (target_index >= items.length) { + target_index = 0; // fallback to first item + } + + const target = items[target_index]; + const others = items.filter((_, index) => index !== target_index); + const shuffled_others = others.sort(() => 0.5 - Math.random()); + + // Create choices array with target and distractors + const choices = [target, ...shuffled_others.slice(0, num_choices - 1)]; + const shuffled_choices = choices.sort(() => 0.5 - Math.random()); + + // Find where the target ended up after shuffling + const correct_choice_index = shuffled_choices.findIndex(choice => choice === target); + + return { + target: target, + choices: shuffled_choices, + correct_answer: correct_choice_index, + target_index: target_index + }; +} + +/** + * Creates instruction pages with configurable text and TTS support + * Based on the pattern-comparison-task implementation + */ +function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false) { + const instruction_timeline = []; + + // Create each instruction page as a separate trial + instruction_pages_data.forEach((page_data, page_index) => { + + // Build page text for TTS - combine all text elements + const page_text = [ + page_data.header, + page_data.header2, + page_data.description, + page_data.task_explanation, + page_data.performance_note, + page_data.strategy_title, + page_data.strategy_intro, + ...(page_data.strategy_points || []), + page_data.start_prompt, + ].filter(Boolean).join(' '); // Remove undefined strings by filtering out falsy values + + instruction_timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

${page_data.header || ""}

+

${page_data.header2 || ""}

+

${page_data.description || ""}

+

${page_data.task_explanation || ""}

+

${page_data.performance_note || ""}

+ ${page_data.strategy_title ? `

${page_data.strategy_title}

` : ""} + ${page_data.strategy_intro ? `

${page_data.strategy_intro}

` : ""} + ${page_data.strategy_points ? ` +
    + ${page_data.strategy_points.map(point => `
  • ${point}
  • `).join('')} +
+ ` : ""} + ${page_data.start_prompt ? `

${page_data.start_prompt}

` : ""} +
+ `, + choices: page_data.buttons, + margin_horizontal: '15px', + margin_vertical: '10px', + button_html: function(choice, choice_index) { + // Check if custom button HTML is provided for this page + if ('button_html' in page_data && page_data.button_html && page_data.button_html[choice_index]) { + return page_data.button_html[choice_index].replace('{choice}', choice); + } + // Default button styling + return ``; + }, + on_start: enable_tts ? function() { + // Stop any ongoing speech first + speechSynthesis.cancel(); + // Wait for voices to load, then speak + if (speechSynthesis.getVoices().length === 0) { + speechSynthesis.addEventListener('voiceschanged', () => { + if (page_text.trim()) { + speakText(page_text); + } + }, { once: true }); + } else { + if (page_text.trim()) { + speakText(page_text); + } + } + } : undefined, + on_finish: function(data: any) { + // Stop speech when moving to next page + speechSynthesis.cancel(); + }, + data: { + page_index: page_index, + task: 'instruction-page' + } + }); + }); + + return { + timeline: instruction_timeline + }; +} + +/** + * Creates a practice round with voice instructions and visual demonstrations + * This helps participants understand the task before the actual trials + */ +function createPracticeRound(items: string[], enable_tts: boolean = false) { + const practice_timeline = []; + + // Create a practice trial set using the first test item + const practice_set = createTrialSet(items, 0, 4); + + // Practice instruction screen + practice_timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

${trial_text.practice_header}

+

We'll now do a practice round to show you how the task works.

+
+ `, + choices: [trial_text.continue_button], + button_html: function(choice) { + return ``; + }, + data: { + task: 'practice-instruction' + } + }); + + // Practice trial with voice instruction "Look at this picture" + practice_timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+
+

${trial_text.practice_look_instruction}

+
+
+
+ ${practice_set.target} +
+
+
+ `, + choices: [], + trial_duration: 3000, // Show for 3 seconds + on_start: function() { + if (enable_tts) { + // Wait for voices to load if needed + if (speechSynthesis.getVoices().length === 0) { + speechSynthesis.addEventListener('voiceschanged', () => { + speakText(trial_text.practice_look_instruction); + }, { once: true }); + } else { + speakText(trial_text.practice_look_instruction); + } + } + }, + data: { + task: 'practice-target-demo' + } + }); + + // Practice trial with voice instruction "We are going to tap the picture down here" + practice_timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+
+

${trial_text.practice_tap_instruction}

+
+
+
+ ${practice_set.target} +
+
+
+ `, + choices: practice_set.choices.map((_, i) => i.toString()), + button_html: function(choice, choice_index) { + return ``; + }, + trial_duration: 4000, // Show for 4 seconds + on_start: function() { + if (enable_tts) { + // Wait for voices to load if needed + if (speechSynthesis.getVoices().length === 0) { + speechSynthesis.addEventListener('voiceschanged', () => { + speakText(trial_text.practice_tap_instruction); + }, { once: true }); + } else { + speakText(trial_text.practice_tap_instruction); + } + } + }, + on_load: function() { + // Set CSS custom property for number of choices for dynamic sizing + const btnGroup = document.querySelector('.jspsych-btn-group, #jspsych-html-button-response-btngroup') as HTMLElement; + if (btnGroup) { + btnGroup.style.setProperty('--num-choices', practice_set.choices.length.toString()); + } + + // Add flashing animation for practice demo (1.5 seconds total) + setTimeout(() => { + const buttons = document.querySelectorAll('.choice-option'); + buttons.forEach(button => { + button.classList.add('flash-choices'); + // Remove flash class after 1.5 seconds + setTimeout(() => { + button.classList.remove('flash-choices'); + }, 1500); + }); + }, 500); + }, + data: { + task: 'practice-choices-demo' + } + }); + + return practice_timeline; +} + +/** + * Creates ready screen asking if user is ready for the actual test + */ +function createReadyScreen() { + return { + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

Practice Complete!

+

${trial_text.practice_complete_message}

+
+ `, + choices: [trial_text.ready_button], + button_html: function(choice) { + return ``; + }, + data: { + task: 'ready-screen' + } + }; +} + +/** + * Generates trials for the main speeded matching task + * Each trial presents a target and multiple choice options + */ +function generateTrials(config: SpeedMatchingConfig) { + const items = config.test_items || test_items; + const num_trials = config.num_trials || 20; + const num_choices = config.num_choices || 4; + const trials = []; + + for (let i = 0; i < num_trials; i++) { + // Randomly select a target item + const target_index = Math.floor(Math.random() * items.length); + const trial_set = createTrialSet(items, target_index, num_choices); + + trials.push({ + target: trial_set.target, + choices: trial_set.choices, + correct_answer: trial_set.correct_answer, + target_index: trial_set.target_index, + trial_number: i + 1 + }); + } + + return trials; +} + +/** + * Main function to create the complete speeded matching timeline + * Includes instructions, practice, and test phases based on configuration + */ +export function createTimeline(jsPsych: JsPsych, config: SpeedMatchingConfig = {}) { + const { + enable_tts = true, + trial_timeout = 10000, + inter_trial_interval = 500, + show_instructions = true, + show_practice = true, + instruction_texts = instruction_pages + } = config; + + const items = config.test_items || test_items; + const trials = generateTrials(config); + const timeline = []; + + // Add instructions if requested + if (show_instructions) { + const instructions = createInstructions(instruction_texts, enable_tts); + timeline.push(instructions); + } + + // Add practice round if requested + if (show_practice) { + const practice_round = createPracticeRound(items, enable_tts); + practice_round.forEach(trial => timeline.push(trial)); + + // Add ready screen after practice + timeline.push(createReadyScreen()); + } + + // Create main task trials + trials.forEach((trial, index) => { + // Main trial + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+
+

${trial_text.main_task_prompt}

+
+
+
+ ${trial.target} +
+
+
+ `, + choices: trial.choices.map((_, i) => i.toString()), + button_html: function(choice, choice_index) { + return ``; + }, + trial_duration: trial_timeout, + on_load: function() { + // Set CSS custom property for number of choices for dynamic sizing + const btnGroup = document.querySelector('.jspsych-btn-group, #jspsych-html-button-response-btngroup') as HTMLElement; + if (btnGroup) { + btnGroup.style.setProperty('--num-choices', trial.choices.length.toString()); + } + + // Add click handlers for visual feedback + const choices = document.querySelectorAll('.choice-option'); + choices.forEach((choice, index) => { + choice.addEventListener('click', function() { + choice.classList.add('selected'); + }); + }); + }, + data: { + task: 'speeded-matching-trial', + trial_number: trial.trial_number, + correct_answer: trial.correct_answer, + target_index: trial.target_index, + target: trial.target, + choices: trial.choices + }, + on_finish: function(data: any) { + // Calculate accuracy and response time + data.correct = data.response === data.correct_answer; + data.reaction_time = data.rt; + + // Stop any ongoing speech when trial ends + speechSynthesis.cancel(); + } + }); + + // Inter-trial interval (fixation cross) + if (inter_trial_interval > 0 && index < trials.length - 1) { + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: `
${trial_text.fixation_cross}
`, + choices: [], + trial_duration: inter_trial_interval, + data: { + task: 'inter-trial-interval' + } + }); + } + }); + + // End screen + timeline.push({ + type: HtmlButtonResponsePlugin, + stimulus: ` +
+

${trial_text.task_complete_header}

+

${trial_text.task_complete_message}

+
+ `, + choices: [trial_text.end_button], + button_html: function(choice) { + return ``; + }, + data: { + task: 'end-screen' + } + }); + + return { + timeline: timeline + }; +} + +export const timelineUnits = { + instructions: "Instructions for the speeded matching task", + practice: "Practice round with voice instructions and demonstrations", + readyScreen: "Confirmation screen before starting the main task", + trial: "Single speeded matching trial with target and choice options", + interTrialInterval: "Fixation cross between trials", + endScreen: "Task completion screen" +} + +export const utils = { + generateTrials, + createInstructions, + createPracticeRound, + createReadyScreen, + speakText, + createTrialSet, + getRandomTestItems, + /** Calculate accuracy and reaction time statistics */ + calculatePerformance: function(data: any[]) { + const trial_data = data.filter(d => d.task === 'speeded-matching-trial'); + const correct = trial_data.filter(d => d.correct).length; + const total = trial_data.length; + const accuracy = total > 0 ? (correct / total) * 100 : 0; + + const valid_rts = trial_data.filter(d => d.correct && d.rt !== null).map(d => d.rt); + const mean_rt = valid_rts.length > 0 ? valid_rts.reduce((a, b) => a + b, 0) / valid_rts.length : null; + + // Calculate performance by target type + const target_performance = {}; + trial_data.forEach(trial => { + const target_index = trial.target_index; + if (!target_performance[target_index]) { + target_performance[target_index] = { + correct: 0, + total: 0, + reaction_times: [] + }; + } + target_performance[target_index].total++; + if (trial.correct) { + target_performance[target_index].correct++; + if (trial.rt !== null) { + target_performance[target_index].reaction_times.push(trial.rt); + } + } + }); + + return { + overall: { + accuracy, + mean_reaction_time: mean_rt, + total_trials: total, + correct_trials: correct + }, + by_target: target_performance + }; + } +} + +// Export text and test items for external use +export { trial_text, instruction_pages, test_items, createInstructions } + +// Default export for convenience +export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/speeded-matching/src/styles.css b/packages/speeded-matching/src/styles.css new file mode 100644 index 00000000..4488a934 --- /dev/null +++ b/packages/speeded-matching/src/styles.css @@ -0,0 +1,266 @@ +/* Speeded Matching Task - Minimal Styles */ + +.speeded-matching-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + font-family: Arial, sans-serif; + padding: 2vmin; +} + +/* Instructions */ +.speeded-matching-instructions-container { + max-width: 90vw; + margin: 0 auto; + padding: 5vmin; + background-color: white; + border-radius: 2vmin; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); + text-align: center; +} + +.speeded-matching-instructions-container h1 { + color: #2c3e50; + margin-bottom: 3vmin; + font-size: 6vmin; +} + +.speeded-matching-instructions-container h2 { + color: #34495e; + margin-bottom: 2vmin; + font-size: 4vmin; +} + +.speeded-matching-instructions-container p { + color: #555; + line-height: 1.6; + margin-bottom: 2vmin; + font-size: 3vmin; +} + +.speeded-matching-instructions-container ul { + text-align: left; + max-width: 80%; + margin: 3vmin auto; +} + +.speeded-matching-instructions-container li { + margin-bottom: 1vmin; + color: #555; + font-size: 3vmin; +} + +.start-prompt { + font-weight: bold; + color: #2980b9 !important; + font-size: 4vmin !important; + margin-top: 4vmin !important; +} + +.speeded-matching-continue-button { + background-color: #3498db; + color: white; + border: none; + padding: 3vmin 6vmin; + font-size: 3vmin; + border-radius: 1vmin; + cursor: pointer; + margin: 2vmin; + transition: background-color 0.3s; + min-height: 48px; +} + +.speeded-matching-continue-button:hover { + background-color: #2980b9; +} + +/* Task instructions */ +.task-instructions { + margin-bottom: 3vmin; + text-align: center; +} + +.task-instructions p { + font-size: 4vmin; + color: #2c3e50; + font-weight: 600; + margin: 0; +} + +/* Target stimulus */ +.target-container { + margin-bottom: 4vmin; + text-align: center; +} + +.target-stimulus { + width: 30vmin; + height: 30vmin; + border: 4px solid #2c3e50; + border-radius: 2vmin; + background-color: white; + display: inline-flex; + align-items: center; + justify-content: center; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); + margin: 2vmin auto; +} + +.target-stimulus.flash { + animation: flash-border 0.5s ease-in-out 3; +} + +@keyframes flash-border { + 0%, 100% { + border-color: #2c3e50; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); + } + 50% { + border-color: #e74c3c; + box-shadow: 0 2vmin 4vmin rgba(231, 76, 60, 0.4); + } +} + +/* Button layout - flexible responsive grid */ +.jspsych-btn-group, +.jspsych-content .jspsych-btn-group, +#jspsych-html-button-response-btngroup { + display: flex !important; + flex-wrap: wrap !important; + gap: 2vmin !important; + max-width: 95vw !important; + margin: 3vmin auto !important; + justify-content: center !important; + align-items: center !important; +} + + +/* Choice buttons - dynamic sizing based on available space */ +.choice-option { + border: 2px solid #bdc3c7 !important; + border-radius: 2vmin !important; + background-color: white !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + transition: all 0.3s ease !important; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1) !important; + padding: 1.5vmin !important; + margin: 0 !important; + /* Dynamic sizing based on container width and number of items */ + flex: 1 1 auto !important; + min-height: 18vmin !important; + max-height: 35vmin !important; + height: calc(100vh / var(--num-choices, 4) - 2vmin) !important; + aspect-ratio: 1 !important; +} + +@media (max-width: 80vmin) { + .choice-option { + min-width: 12vmin !important; + max-width: 40vmin !important; + width: calc(80vw / var(--num-choices, 4) - 2vmin) !important; + } +} + +/* Responsive sizing for different screen sizes */ +@media (max-width: 130vmin) { + .choice-option { + min-width: 35vmin !important; + max-width: 40vmin !important; + width: calc(85vw / var(--num-choices, 4) - 2vmin) !important; + aspect-ratio: 1.1; /* Slightly wider for better readability */ + } +} + +.choice-option:hover { + border-color: #3498db !important; + transform: translateY(-0.5vmin) !important; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15) !important; +} + +.choice-option.flash-choices { + animation: flash-choices 0.5s ease-in-out 3 !important; +} + +@keyframes flash-choices { + 0%, 100% { + border-color: #bdc3c7 !important; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1) !important; + } + 50% { + border-color: #f39c12 !important; + box-shadow: 0 2vmin 3vmin rgba(243, 156, 18, 0.3) !important; + } +} + +/* SVG sizing */ +.target-stimulus svg { + width: 100% !important; + height: 100% !important; +} + +.choice-option svg { + width: 100% !important; + height: 100% !important; +} + +/* End screen */ +.speeded-matching-end-screen { + text-align: center; + max-width: 90vw; + margin: 0 auto; + padding: 5vmin; + background-color: white; + border-radius: 2vmin; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); +} + +.speeded-matching-end-screen h2 { + color: #27ae60; + font-size: 5vmin; + margin-bottom: 3vmin; +} + +.speeded-matching-end-screen p { + color: #555; + font-size: 3vmin; + line-height: 1.6; +} + +/* Fixation cross, set to invisible by default */ +.speeded-matching-fixation { + display: flex; + align-items: center; + justify-content: center; + font-size: 8vmin; + color: #ffffff; /* White color for invisibility */ + font-weight: bold; + min-height: 20vmin; +} + +/* Ready screen */ +.ready-screen { + text-align: center; + max-width: 90vw; + margin: 0 auto; + padding: 5vmin; + background-color: white; + border-radius: 2vmin; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); +} + +.ready-screen h2 { + color: #2c3e50; + font-size: 5vmin; + margin-bottom: 3vmin; +} + +.ready-screen p { + color: #555; + font-size: 3vmin; + line-height: 1.6; + margin-bottom: 4vmin; +} \ No newline at end of file diff --git a/packages/speeded-matching/src/test-items.ts b/packages/speeded-matching/src/test-items.ts new file mode 100644 index 00000000..084ef77e --- /dev/null +++ b/packages/speeded-matching/src/test-items.ts @@ -0,0 +1,116 @@ +/* This file contains the animal SVGs used in the speeded matching task. + * Researchers can modify these SVGs to change the visual stimuli used in the experiment. + * Each SVG should be a complete SVG element as a string. + * + * To add new animal SVGs: + * 1. Go to https://www.svgrepo.com/collection/animal-portrait-vectors/ + * 2. Select an animal SVG + * 3. Click "Edit SVG" + * 4. Copy the SVG code + * 5. Add it to the array below + * + * Make sure all SVGs have similar dimensions for consistent display. + */ + +export const test_items = [ + // Cat SVG + ` + + + + + + + `, + + // Dog SVG + ` + + + + + + + `, + + // Bird SVG + ` + + + + + + `, + + // Fish SVG + ` + + + + + + `, + + // Rabbit SVG + ` + + + + + + + + `, + + // Elephant SVG + ` + + + + + + + `, + + // Lion SVG + ` + + + + + + + `, + + // Bear SVG + ` + + + + + + + `, + + // Frog SVG + ` + + + + + + + `, + + // Butterfly SVG + ` + + + + + + + + + ` +]; \ No newline at end of file diff --git a/packages/speeded-matching/src/text.ts b/packages/speeded-matching/src/text.ts new file mode 100644 index 00000000..35ac963f --- /dev/null +++ b/packages/speeded-matching/src/text.ts @@ -0,0 +1,78 @@ +/* This file contains the configurable text used in the speeded matching timeline. + * Researchers can modify these texts to change the language or instructions. + */ + +export const trial_text = { + // Button texts + continue_button: "Continue", + start_button: "Start", + ready_button: "I'm Ready", + end_button: "End", + + // Task completion messages + task_complete_header: "Task Complete!", + task_complete_message: "Thank you for participating in the speeded matching task.", + + // Practice phase text + practice_header: "Practice Round", + practice_look_instruction: "Look at this picture", + practice_tap_instruction: "We are going to tap the matching picture below", + practice_complete_message: "Practice complete! Are you ready for the actual test?", + + // Main task instructions + main_task_prompt: "", + + // Fixation and inter-trial + fixation_cross: "+", + + // Feedback messages (optional) + correct_feedback: "Correct!", + incorrect_feedback: "Try again", + + // Timing messages + too_slow_message: "Please respond faster", +} + +/* + * This is an array of page objects that have configurable texts for the instruction pages + * before the actual trials. Researchers can modify these instructions to change the + * task description, add new instruction pages, or translate to different languages. + * + * Each page object can contain: + * - header: Main title (displays in

) + * - header2: Subtitle (displays in

) + * - description: Main description text (displays in

) + * - task_explanation: Detailed task explanation (displays in

) + * - performance_note: Performance tips (displays in

) + * - strategy_title: Strategy section title (displays in

) + * - strategy_intro: Introduction to strategy (displays in

) + * - strategy_points: Array of strategy bullet points (displays as

  • ) + * - start_prompt: Final instruction before starting (displays in

    ) + * - buttons: Array of button text options + * - button_html: Custom HTML for buttons (optional) + */ + +export const instruction_pages = [ + { + header: "Speeded Matching Task", + header2: "Visual Pattern Matching", + description: "In this task, you will see a target picture at the top of the screen and four choice pictures below.", + task_explanation: "Your job is to quickly identify which of the four pictures below matches the target picture at the top.", + performance_note: "Try to respond as quickly and accurately as possible. Speed and accuracy are both important.", + start_prompt: "Click continue to learn more about the task.", + buttons: ["Continue"], + }, + { + strategy_title: "Instructions", + strategy_intro: "For each trial:", + strategy_points: [ + "Look at the target picture at the top of the screen", + "Compare it with the four choice pictures below", + "Click on the picture that matches the target", + "Respond as quickly and accurately as possible", + "A brief outline will appear when you make your selection" + ], + start_prompt: "Click continue to start the practice round.", + buttons: ["Continue"], + } +]; \ No newline at end of file diff --git a/packages/speeded-matching/tsconfig.json b/packages/speeded-matching/tsconfig.json new file mode 100644 index 00000000..33655f70 --- /dev/null +++ b/packages/speeded-matching/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@jspsych/config/tsconfig.contrib.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": [ + "src", + "../cli/src/build.js" + ] +} \ No newline at end of file From 56acbd7648031c15c02fd0d36c9498a166b79960 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Wed, 25 Jun 2025 15:47:57 -0400 Subject: [PATCH 13/37] added data printing and fixed params --- packages/speeded-matching/examples/index.html | 17 +- packages/speeded-matching/src/index copy.ts | 527 ++++++++++++++++++ packages/speeded-matching/src/index.ts | 29 +- packages/speeded-matching/src/text.ts | 2 + 4 files changed, 546 insertions(+), 29 deletions(-) create mode 100644 packages/speeded-matching/src/index copy.ts diff --git a/packages/speeded-matching/examples/index.html b/packages/speeded-matching/examples/index.html index d1ad5767..8808914f 100644 --- a/packages/speeded-matching/examples/index.html +++ b/packages/speeded-matching/examples/index.html @@ -15,18 +15,9 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/flanker-inhibitory/jest.config.js b/packages/flanker-inhibitory/jest.config.js new file mode 100644 index 00000000..bd1aacef --- /dev/null +++ b/packages/flanker-inhibitory/jest.config.js @@ -0,0 +1 @@ +module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/flanker-inhibitory/package.json b/packages/flanker-inhibitory/package.json new file mode 100644 index 00000000..4590ff3f --- /dev/null +++ b/packages/flanker-inhibitory/package.json @@ -0,0 +1,35 @@ +{ + "name": "@jspsych-timelines/flanker-inhibitory", + "version": "0.0.1", + "description": "The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and", + "type": "module", + "main": "dist/index.mjs", + "types": "dist/index.d.ts", + "unpkg": "dist/index.browser.min.js", + "scripts": { + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineFlankerInhibitory" + }, + "repository": { + "type": "git", + "url": "https://github.com/farhat60/jspsych-timelines/", + "directory": "packages" + }, + "keywords": [ + "jsPsych" + ], + "author": { + "name": "Hunter Farhat", + "url": "farhat60" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/farhat60/jspsych-timelines//issues" + }, + "homepage": "https://github.com/jspsych/jspsych-timelines/packages/flanker-inhibitory/README.md", + "devDependencies": { + "jspsych": "^8.0.0", + "@jspsych/config": "^3.2.2", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + } \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts new file mode 100644 index 00000000..e3881ab1 --- /dev/null +++ b/packages/flanker-inhibitory/src/index.ts @@ -0,0 +1,507 @@ +import { JsPsych } from "jspsych"; +import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; +import { trial_text, instruction_pages } from './text'; +import { layered_stimuli, fish_only, arrow_only } from './stimuli'; + +// ============================================================================ +// LAYERED SVG UTILITIES +// ============================================================================ + +// ============================================================================ +// SVG UTILITIES +// ============================================================================ + +// Function to horizontally flip an SVG - works for all SVGs +function flipSVG(svgString: string): string { + // Parse the SVG to extract viewBox and content + const svgMatch = svgString.match(/]*>/); + if (!svgMatch) return svgString; + + const svgTag = svgMatch[0]; + const svgContent = svgString.replace(svgTag, '').replace('', ''); + + // Extract viewBox if it exists + const viewBoxMatch = svgTag.match(/viewBox="([^"]+)"/); + let viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 48 48'; + const viewBoxParts = viewBox.split(' '); + const vbWidth = parseFloat(viewBoxParts[2]) || 48; + + // Extract width and height + const widthMatch = svgTag.match(/width="([^"]+)"/); + const heightMatch = svgTag.match(/height="([^"]+)"/); + const width = widthMatch ? widthMatch[1] : '48'; + const height = heightMatch ? heightMatch[1] : '48'; + + // Check if SVG already has a horizontal flip transform + const hasFlipTransform = svgTag.includes('transform="matrix(-1,') || + svgTag.includes('transform="matrix(-1 ') || + svgTag.includes('transform="scale(-1'); + + // If already flipped, remove the flip transform instead of adding another + if (hasFlipTransform) { + // Remove existing flip transforms and return the content directly + let cleanedTag = svgTag + .replace(/transform="[^"]*"/g, '') + .replace(/\s+/g, ' ') + .trim(); + + // Ensure we have proper attributes + if (!cleanedTag.includes('xmlns')) { + cleanedTag = cleanedTag.replace('`; + } else { + // Create a new SVG with a wrapper group that flips the content + return ` + + ${svgContent} + + `; + } +} + +// Function to layer multiple SVGs on top of each other +function layerSVGs(svgArray: string[]): string { + if (!svgArray || !Array.isArray(svgArray) || svgArray.length === 0) return ''; + if (svgArray.length === 1) return svgArray[0]; + + // Create a container div with relative positioning + let layeredHTML = '

    '; + + svgArray.forEach((svg, index) => { + const style = index === 0 + ? 'position: relative;' + : 'position: absolute; top: 0; left: 0;'; + layeredHTML += `
    ${svg}
    `; + }); + + layeredHTML += '
    '; + return layeredHTML; +} + +// Function to process layered stimuli - creates left/right from SVG arrays +function processLayeredStimuli(stimuli: { left?: string[], right?: string[] } | string[]) { + // If it's a simple array, treat it as right-facing + if (Array.isArray(stimuli)) { + const rightLayered = layerSVGs(stimuli); + const leftLayered = layerSVGs(stimuli.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } + + // If object with left/right arrays + if (stimuli.left && !stimuli.right) { + const leftLayered = layerSVGs(stimuli.left); + const rightLayered = layerSVGs(stimuli.left.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } else if (stimuli.right && !stimuli.left) { + const rightLayered = layerSVGs(stimuli.right); + const leftLayered = layerSVGs(stimuli.right.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } else if (stimuli.left && stimuli.right) { + return { + left: layerSVGs(stimuli.left), + right: layerSVGs(stimuli.right) + }; + } else { + // Neither provided, use default layered + const rightLayered = layerSVGs(layered_stimuli); + const leftLayered = layerSVGs(layered_stimuli.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } +} + +export const default_stimuli = processLayeredStimuli(layered_stimuli); + +export const fish_stimuli = processLayeredStimuli(fish_only); + +export const arrow_stimuli = processLayeredStimuli(arrow_only); + +// ============================================================================ +// STIMULUS CREATION FUNCTIONS +// ============================================================================ + +export function createFlankerStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli) { + // Handle different input types + let processedStimuli; + if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { + processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); + } else { + // Already processed stimuli with left/right strings + processedStimuli = stimuli as { left: string; right: string }; + } + const center = processedStimuli[direction]; + const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; + + let html = `
    `; + html += `${flanker}`; + html += `${flanker}`; + html += `${center}`; + html += `${flanker}`; + html += `${flanker}`; + html += `
    `; + return html; +} + +export function createPracticeStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli) { + // Handle different input types + let processedStimuli; + if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { + processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); + } else { + // Already processed stimuli with left/right strings + processedStimuli = stimuli as { left: string; right: string }; + } + const center = processedStimuli[direction]; + const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; + + let html = `
    `; + html += `${flanker}`; + html += `${flanker}`; + html += `${center}`; + html += `${flanker}`; + html += `${flanker}`; + html += `
    `; + return html; +} + +// ============================================================================ +// INTERFACES +// ============================================================================ + +export interface FlankerConfig { + stimuli_type?: 'fish' | 'arrow' | 'layered'; + custom_stimuli?: { left?: string[]; right?: string[] }; + svg?: string[]; // Override parameter - array of SVGs to layer + fixation_duration?: number; + show_instructions?: boolean; + show_practice?: boolean; + num_practice?: number; + num_trials?: number; +} + +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +export function calculatePerformance(data: any[]) { + const trial_data = data.filter(d => d.task === 'flanker'); + + if (trial_data.length === 0) { + return { accuracy: 0, mean_rt: 0, total_trials: 0 }; + } + + const correct_trials = trial_data.filter(d => d.correct); + const accuracy = (correct_trials.length / trial_data.length) * 100; + const mean_rt = correct_trials.length > 0 + ? correct_trials.reduce((sum, d) => sum + d.rt, 0) / correct_trials.length + : 0; + + return { + accuracy, + mean_rt, + total_trials: trial_data.length, + correct_trials: correct_trials.length + }; +} + +// ============================================================================ +// MAIN TIMELINE FUNCTION +// ============================================================================ + +export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { + const { + stimuli_type = 'layered', + custom_stimuli, + svg, + fixation_duration = 500, + show_instructions = true, + show_practice = true, + num_practice = 8, + num_trials = 20, + } = config; + + // Determine which stimuli to use based on priority: + // 1. svg override parameter (highest priority) + // 2. custom_stimuli + // 3. stimuli_type selection + let stimuli; + if (svg) { + stimuli = svg; // Use the SVG override array directly + } else if (custom_stimuli) { + stimuli = custom_stimuli; + } else if (stimuli_type === 'arrow') { + stimuli = arrow_stimuli; + } else if (stimuli_type === 'fish') { + stimuli = fish_stimuli; + } else { + stimuli = default_stimuli; // layered by default + } + const timeline = []; + + // Instructions + if (show_instructions) { + instruction_pages.forEach((page, index) => { + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
    + ${page.header ? `

    ${page.header}

    ` : ''} + ${page.header2 ? `

    ${page.header2}

    ` : ''} + ${page.description ? `

    ${page.description}

    ` : ''} + ${page.task_explanation ? `

    ${page.task_explanation}

    ` : ''} + ${page.performance_note ? `

    ${page.performance_note}

    ` : ''} + ${page.strategy_title ? `

    ${page.strategy_title}

    ` : ''} + ${page.strategy_intro ? `

    ${page.strategy_intro}

    ` : ''} + ${page.strategy_points ? ` +
      + ${page.strategy_points.map(point => `
    • ${point}
    • `).join('')} +
    + ` : ''} + ${page.start_prompt ? `

    ${page.start_prompt}

    ` : ''} +
    + `, + choices: page.buttons || [trial_text.continue_button], + data: { task: 'instructions', page: index + 1 } + }); + }); + } + + // Practice + if (show_practice) { + // Practice intro + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
    +

    ${trial_text.practice_header}

    +

    ${trial_text.practice_intro_message}

    +
    + `, + choices: [trial_text.start_button], + data: { task: 'practice-intro' } + }); + + // Practice trials + const practice_variables = [ + {direction: 'left', congruent: true}, + {direction: 'left', congruent: false}, + {direction: 'right', congruent: true}, + {direction: 'right', congruent: false}, + ]; + + const practice_trials = jsPsych.randomization.repeat(practice_variables, Math.floor(num_practice/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(practice_variables, num_practice%4)); + + // Fixation + const practice_fixation = { + type: jsPsychHtmlButtonResponse, + stimulus: `
    ${trial_text.fixation_cross}
    `, + choices: [], + trial_duration: fixation_duration, + data: { task: 'fixation', phase: 'practice' } + }; + + // Practice trial + const practice_trial = { + type: jsPsychHtmlButtonResponse, + stimulus: function() { + const direction = jsPsych.evaluateTimelineVariable('direction'); + const congruent = jsPsych.evaluateTimelineVariable('congruent'); + return ` +
    +
    ${trial_text.main_task_prompt}
    + ${createPracticeStim(direction, congruent, stimuli)} +
    + `; + }, + choices: [trial_text.left_button, trial_text.right_button], + data: { + task: 'flanker', + phase: 'practice', + direction: jsPsych.timelineVariable('direction'), + congruent: jsPsych.timelineVariable('congruent') + }, + on_finish: function(data: any) { + const correct_response = data.direction === 'left' ? 0 : 1; + data.correct = data.response === correct_response; + } + }; + + // Feedback + const practice_feedback = { + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const last_trial = jsPsych.data.get().last(1).values()[0]; + const feedback = last_trial.correct ? trial_text.correct_feedback : trial_text.incorrect_feedback; + return ``; + }, + choices: [], + trial_duration: 1000, + data: { task: 'feedback', phase: 'practice' } + }; + + // Practice timeline + timeline.push({ + timeline: [practice_fixation, practice_trial, practice_feedback], + timeline_variables: practice_trials, + randomize_order: true + }); + + // Practice completion + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); + const performance = calculatePerformance(practice_data); + + return ` +
    +

    ${trial_text.practice_complete_header}

    +

    ${trial_text.practice_complete_message}

    +

    Your accuracy: ${performance.accuracy.toFixed(1)}%

    +
    + `; + }, + choices: [trial_text.continue_button], + data: { task: 'practice-complete' } + }); + } + + // Main task + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
    +

    Main Task

    +

    Now we'll start the real task. Remember to focus on the middle fish!

    +
    + `, + choices: [trial_text.ready_button], + data: { task: 'main-intro' } + }); + + // Main trials + const trial_variables = [ + {direction: 'left', congruent: true}, + {direction: 'left', congruent: false}, + {direction: 'right', congruent: true}, + {direction: 'right', congruent: false}, + ]; + + const main_trials = jsPsych.randomization.repeat(trial_variables, Math.floor(num_trials/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(trial_variables, num_trials%4)); + + // Fixation + const fixation = { + type: jsPsychHtmlButtonResponse, + stimulus: `
    ${trial_text.fixation_cross}
    `, + choices: [], + trial_duration: fixation_duration, + data: { task: 'fixation', phase: 'main' } + }; + + // Main trial + const flanker_trial = { + type: jsPsychHtmlButtonResponse, + stimulus: function() { + const direction = jsPsych.evaluateTimelineVariable('direction'); + const congruent = jsPsych.evaluateTimelineVariable('congruent'); + return ` +
    +
    ${trial_text.main_task_prompt}
    + ${createFlankerStim(direction, congruent, stimuli)} +
    + `; + }, + choices: [trial_text.left_button, trial_text.right_button], + data: { + task: 'flanker', + phase: 'main', + direction: jsPsych.timelineVariable('direction'), + congruent: jsPsych.timelineVariable('congruent') + }, + on_finish: function(data: any) { + const correct_response = data.direction === 'left' ? 0 : 1; + data.correct = data.response === correct_response; + } + }; + + // Main task timeline + timeline.push({ + timeline: [fixation, flanker_trial], + timeline_variables: main_trials, + randomize_order: true + }); + + // Completion + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const main_data = jsPsych.data.get().filter({task: 'flanker', phase: 'main'}).values(); + const performance = calculatePerformance(main_data); + + return ` +
    +

    ${trial_text.task_complete_header}

    +

    ${trial_text.task_complete_message}

    +
    +

    Your Performance:

    +

    Accuracy: ${performance.accuracy.toFixed(1)}%

    +

    Average Response Time: ${performance.mean_rt.toFixed(0)}ms

    +
    +
    + `; + }, + choices: [trial_text.end_button], + data: { task: 'complete' } + }); + + return { + timeline: timeline + }; +} + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const timelineUnits = { + instructions: "Instructions for the flanker inhibitory control task", + practice: "Practice trials with feedback", + main: "Main flanker task trials", + completion: "Task completion screen" +}; + +export const utils = { + calculatePerformance, + createFlankerStim, + createPracticeStim +}; + +// Export text for external use +export { trial_text, instruction_pages } from './text'; + +// Default export +export default { createTimeline, timelineUnits, utils }; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/stimuli.ts b/packages/flanker-inhibitory/src/stimuli.ts new file mode 100644 index 00000000..b9030501 --- /dev/null +++ b/packages/flanker-inhibitory/src/stimuli.ts @@ -0,0 +1,43 @@ +// ============================================================================ +// LAYERED SVG STIMULI +// ============================================================================ + +// Fish SVG (base layer) +const fish_svg = ` + + + + + + + + +`; + +// Arrow SVG (overlay layer) +const arrow_svg = ` + +`; + +// Default layered stimuli - fish with arrow overlay +export const layered_stimuli = [ + fish_svg, + arrow_svg +]; + +// Fish only +export const fish_only = [ + fish_svg +]; + +// Arrow only +export const arrow_only = [ + arrow_svg +]; + +// Custom example - you can add more layers +export const custom_example = [ + fish_svg, + arrow_svg, + // Add more layers here if needed +]; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css new file mode 100644 index 00000000..224e5b35 --- /dev/null +++ b/packages/flanker-inhibitory/src/styles.css @@ -0,0 +1,370 @@ +/* Flanker Inhibitory Control Task Styles - Simple jsPsych approach */ + +/* Instructions */ +.flanker-instructions { + max-width: 90vw; + max-height: 85vh; + margin: 0 auto; + padding: 3vmin; + text-align: left; + line-height: 1.4; + background-color: white; + border-radius: 2vmin; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); + overflow-y: auto; +} + +.flanker-instructions h1 { + color: #2c3e50; + text-align: center; + margin-bottom: 2vmin; + font-size: 4.5vmin; +} + +.flanker-instructions h2 { + color: #34495e; + margin-top: 3vmin; + margin-bottom: 1.5vmin; + font-size: 3.5vmin; +} + +.flanker-instructions p { + color: #555; + margin-bottom: 1.5vmin; + font-size: 3vmin; +} + +.flanker-instructions .performance-note { + background-color: #e8f4fd; + border-left: 4px solid #3498db; + padding: 1.5vmin 2vmin; + margin: 2vmin 0; + font-weight: 500; + border-radius: 1vmin; +} + +.flanker-instructions .start-prompt { + text-align: center; + font-weight: 600; + color: #2c3e50; + margin-top: 2vmin; + font-size: 3.5vmin; +} + +.flanker-instructions ul { + padding-left: 3vmin; + margin: 2vmin 0; +} + +.flanker-instructions li { + margin: 1vmin 0; + font-size: 3vmin; + color: #555; +} + +/* Performance summary */ +.performance-summary { + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 2vmin; + padding: 4vmin; + margin: 4vmin 0; + text-align: left; +} + +.performance-summary h3 { + color: #495057; + margin-bottom: 3vmin; + text-align: center; + font-size: 4.5vmin; +} + +.performance-summary p { + margin: 2vmin 0; + font-size: 3.5vmin; + color: #6c757d; +} + +/* Trial display */ +.flanker-trial { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 50vh; + padding: 3vmin; + gap: 4vmin; +} + +.trial-prompt { + font-size: 4.5vmin; + font-weight: 600; + color: #2c3e50; + text-align: center; + max-width: 90vw; +} + +/* Flanker stimulus */ +.flanker-stim { + display: flex; + align-items: center; + justify-content: center; + gap: 2vmin; + margin: 3vmin 0; + padding: 3vmin; +} + +.flanker-stim span { + display: inline-block; +} + +.flanker-stim svg { + width: 8vmin; + height: 8vmin; + display: block; +} + +/* Practice highlighting */ +.flanker-stim.practice .center.highlighted { + background-color: #fff3cd; + border: 3px solid #ffc107; + border-radius: 50%; + padding: 1.5vmin; + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); + } + 70% { + box-shadow: 0 0 0 2vmin rgba(255, 193, 7, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); + } +} + +/* Fixation cross */ +.fixation { + display: flex; + align-items: center; + justify-content: center; + min-height: 20vh; + font-size: 6vmin; + font-weight: bold; + color: #2c3e50; +} + +/* Feedback */ +.feedback { + display: flex; + align-items: center; + justify-content: center; + min-height: 20vh; + font-size: 6vmin; + font-weight: bold; + text-align: center; + padding: 3vmin; +} + +.feedback.correct { + color: #27ae60; +} + +.feedback.incorrect { + color: #e74c3c; +} + +/* ============================================================================ */ +/* JSPSYCH BUTTON OVERRIDES FOR MOBILE */ +/* ============================================================================ */ + +/* Button group layout */ +.jspsych-btn-group, +.jspsych-content .jspsych-btn-group, +#jspsych-html-button-response-btngroup { + display: flex !important; + flex-wrap: wrap !important; + gap: 3vmin !important; + max-width: 95vw !important; + margin: 4vmin auto !important; + justify-content: center !important; + align-items: center !important; +} + +/* All jsPsych buttons - mobile-friendly */ +.jspsych-btn { + border: 3px solid #3498db !important; + border-radius: 3vmin !important; + background-color: white !important; + color: #2c3e50 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + transition: all 0.3s ease !important; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15) !important; + padding: 3vmin 6vmin !important; + margin: 1vmin !important; + font-family: inherit !important; + font-weight: bold !important; + font-size: 4vmin !important; + + /* Minimum touch target size */ + min-width: 15vmin !important; + min-height: 12vmin !important; +} + +/* Arrow response buttons - bigger for main task */ +.jspsych-btn:only-child { + /* Single buttons like Continue */ + min-width: 25vmin !important; + border-color: #27ae60 !important; + background-color: #2ecc71 !important; + color: white !important; +} + +/* For Left/Right arrow buttons (when there are exactly 2 buttons) */ +.jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + min-width: 20vmin !important; + min-height: 20vmin !important; + font-size: 8vmin !important; + flex: 1 1 auto !important; + max-width: 35vmin !important; +} + +/* Button hover states */ +.jspsych-btn:hover { + border-color: #2980b9 !important; + transform: translateY(-1vmin) !important; + box-shadow: 0 3vmin 4vmin rgba(0, 0, 0, 0.2) !important; + background-color: #ecf0f1 !important; +} + +.jspsych-btn:only-child:hover { + background-color: #27ae60 !important; + border-color: #229954 !important; + color: white !important; +} + +.jspsych-btn:active { + transform: translateY(0) !important; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.15) !important; +} + +/* ============================================================================ */ +/* RESPONSIVE DESIGN */ +/* ============================================================================ */ + +/* Small screens */ +@media (max-width: 480px) { + .flanker-stim { + gap: 1vmin; + padding: 2vmin; + } + + .flanker-stim svg { + width: 6vmin; + height: 6vmin; + } + + .trial-prompt { + font-size: 4vmin; + } + + .fixation { + font-size: 10vmin; + min-height: 25vh; + } + + .jspsych-btn { + font-size: 3.5vmin !important; + } + + .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + font-size: 7vmin !important; + min-width: 18vmin !important; + min-height: 18vmin !important; + } +} + +/* Medium screens */ +@media (min-width: 481px) and (max-width: 768px) { + .flanker-stim svg { + width: 9vmin; + height: 9vmin; + } + + .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + font-size: 8vmin !important; + min-width: 22vmin !important; + min-height: 22vmin !important; + } +} + +/* Large screens */ +@media (min-width: 769px) { + .flanker-stim svg { + width: 10vmin; + height: 10vmin; + } + + .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + font-size: 6vmin !important; + min-width: 15vmin !important; + min-height: 15vmin !important; + max-width: 20vmin !important; + } +} + +/* ============================================================================ */ +/* ACCESSIBILITY */ +/* ============================================================================ */ + +/* High contrast */ +@media (prefers-contrast: high) { + .flanker-stim svg { + filter: contrast(2); + } + + .performance-note { + background-color: #ffffff; + border-color: #000000; + color: #000000; + } + + .jspsych-btn { + border-color: #000000 !important; + background-color: #ffffff !important; + color: #000000 !important; + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + .flanker-stim.practice .center.highlighted { + animation: none; + } + + .jspsych-btn { + transition: none !important; + } +} + +/* Touch devices */ +@media (hover: none) and (pointer: coarse) { + .jspsych-btn:hover { + transform: none !important; + } + + .jspsych-btn:active { + background-color: #3498db !important; + color: white !important; + } + + .jspsych-btn:only-child:active { + background-color: #27ae60 !important; + color: white !important; + } +} \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts new file mode 100644 index 00000000..eafaeb9a --- /dev/null +++ b/packages/flanker-inhibitory/src/text.ts @@ -0,0 +1,92 @@ +/* This file contains the configurable text used in the flanker inhibitory timeline. + * Researchers can modify these texts to change the language or instructions. + */ + +export const trial_text = { + // Button texts + continue_button: "Continue", + start_button: "Start", + ready_button: "I'm Ready", + end_button: "End", + practice_button: "Practice Again", + left_button: "←", + right_button: "→", + + // Task completion messages + task_complete_header: "Task Complete!", + task_complete_message: "Thank you for participating in the flanker inhibitory control task.", + + // Practice phase text + practice_header: "Practice Round", + practice_intro_message: "Let's practice! Look at the middle fish and click the button that shows which way it's swimming.", + practice_complete_header: "Practice Complete!", + practice_complete_message: "Great job! Now you're ready for the real task.", + practice_failed_message: "Let's try the practice again. Remember to focus on the middle fish only.", + + // Main task instructions + main_task_prompt: "Look at the middle fish. Click the button for the direction it's swimming.", + + // Fixation and inter-trial + fixation_cross: "+", + + // Feedback messages + correct_feedback: "Correct!", + incorrect_feedback: "Incorrect", + too_slow_message: "Please respond faster", + + // Progress messages + block_progress: "Block {current} of {total}", + trial_progress: "Trial {current} of {total}", +}; + +/* + * This is an array of page objects that have configurable texts for the instruction pages + * before the actual trials. Researchers can modify these instructions to change the + * task description, add new instruction pages, or translate to different languages. + * + * Each page object can contain: + * - header: Main title (displays in

    ) + * - header2: Subtitle (displays in

    ) + * - description: Main description text (displays in

    ) + * - task_explanation: Detailed task explanation (displays in

    ) + * - performance_note: Performance tips (displays in

    ) + * - strategy_title: Strategy section title (displays in

    ) + * - strategy_intro: Introduction to strategy (displays in

    ) + * - strategy_points: Array of strategy bullet points (displays as

    • ) + * - start_prompt: Final instruction before starting (displays in

      ) + * - buttons: Array of button text options + * - button_html: Custom HTML for buttons (optional) + */ + +export const instruction_pages = [ + { + header: "Flanker Inhibitory Control Task", + header2: "Attention and Focus Test", + description: "In this task, you will see a group of fish swimming on the screen.", + task_explanation: "Your job is to focus only on the middle fish and ignore the other fish around it.", + performance_note: "Look at which direction the middle fish is swimming and click the corresponding button.", + start_prompt: "Click continue to learn more about the task.", + buttons: ["Continue"], + }, + { + strategy_title: "Instructions", + strategy_intro: "For each trial:", + strategy_points: [ + "Look at the middle fish only - ignore the other fish", + "See which direction the middle fish is swimming", + "Click the ← button if it's swimming left", + "Click the → button if it's swimming right", + "Try to respond as quickly and accurately as possible" + ], + start_prompt: "The other fish around the middle fish might be swimming in different directions - don't let them distract you!", + buttons: ["Continue"], + }, + { + header2: "Button Instructions", + description: "Use these buttons to respond:", + task_explanation: "← Left Button = Middle fish swimming left
      → Right Button = Middle fish swimming right", + performance_note: "Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions.", + start_prompt: "Click continue to start the practice round.", + buttons: ["Continue"], + } +]; \ No newline at end of file diff --git a/packages/flanker-inhibitory/tsconfig.json b/packages/flanker-inhibitory/tsconfig.json new file mode 100644 index 00000000..33655f70 --- /dev/null +++ b/packages/flanker-inhibitory/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "@jspsych/config/tsconfig.contrib.json", + "compilerOptions": { + "baseUrl": "." + }, + "include": [ + "src", + "../cli/src/build.js" + ] +} \ No newline at end of file From ad797a315f68838c36bc5b6f72c5a97c41064f17 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Fri, 27 Jun 2025 10:28:14 -0400 Subject: [PATCH 17/37] added 42 tests that all pass, need to revise --- .claude/settings.local.json | 16 + package-lock.json | 10 + package.json | 1 + .../{jest.config.js => jest.config.cjs} | 0 packages/flanker-inhibitory/src/index.spec.ts | 410 ++++++++++++++++++ 5 files changed, 437 insertions(+) create mode 100644 .claude/settings.local.json rename packages/flanker-inhibitory/{jest.config.js => jest.config.cjs} (100%) create mode 100644 packages/flanker-inhibitory/src/index.spec.ts diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 00000000..ffb44bec --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,16 @@ +{ + "permissions": { + "allow": [ + "WebFetch(domain:nihtoolbox.org)", + "WebFetch(domain:pmc.ncbi.nlm.nih.gov)", + "WebFetch(domain:www.svgrepo.com)", + "Bash(rg:*)", + "Bash(find:*)", + "Bash(ls:*)", + "WebFetch(domain:resources.nihtoolbox.org)", + "Bash(npm run build:*)", + "Bash(npm test)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 715592ca..04ae6c23 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "templates/cli" ], "dependencies": { + "@jspsych/plugin-html-button-response": "^2.1.0", "@jspsych/test-utils": "^1.2.0", "chokidar": "^3.6.0", "gulp": "^5.0.0", @@ -2592,6 +2593,15 @@ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", "dev": true }, + "node_modules/@jspsych/plugin-html-button-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-button-response/-/plugin-html-button-response-2.1.0.tgz", + "integrity": "sha512-AhJPePA8s/ount+QYtqfgMtEkz7g+j+bnhQ9XqxfsnK2f5MdnlakYtpjxW9YgFx9y0Gs7yb7OZk1+omcKlzaEQ==", + "license": "MIT", + "peerDependencies": { + "jspsych": ">=7.1.0" + } + }, "node_modules/@jspsych/plugin-html-keyboard-response": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", diff --git a/package.json b/package.json index f1bed32b..24f6b70d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ ] }, "dependencies": { + "@jspsych/plugin-html-button-response": "^2.1.0", "@jspsych/test-utils": "^1.2.0", "chokidar": "^3.6.0", "gulp": "^5.0.0", diff --git a/packages/flanker-inhibitory/jest.config.js b/packages/flanker-inhibitory/jest.config.cjs similarity index 100% rename from packages/flanker-inhibitory/jest.config.js rename to packages/flanker-inhibitory/jest.config.cjs diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts new file mode 100644 index 00000000..0e1e2283 --- /dev/null +++ b/packages/flanker-inhibitory/src/index.spec.ts @@ -0,0 +1,410 @@ +import { initJsPsych } from "jspsych"; +import { + createTimeline, + createFlankerStim, + createPracticeStim, + calculatePerformance, + default_stimuli, + fish_stimuli, + arrow_stimuli, + timelineUnits, + utils, + trial_text, + instruction_pages, + FlankerConfig +} from "./index"; + +describe("Flanker Inhibitory Control Task", () => { + let jsPsych: any; + + beforeEach(() => { + jsPsych = initJsPsych(); + }); + + describe("createTimeline", () => { + it("should return a timeline object with timeline array", () => { + const timeline = createTimeline(jsPsych); + expect(timeline).toBeDefined(); + expect(timeline.timeline).toBeDefined(); + expect(Array.isArray(timeline.timeline)).toBe(true); + }); + + it("should create timeline with default parameters", () => { + const timeline = createTimeline(jsPsych); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should create timeline without instructions when disabled", () => { + const timeline = createTimeline(jsPsych, { show_instructions: false }); + expect(timeline.timeline.length).toBeGreaterThan(0); + // Should have fewer trials than with instructions + const timelineWithInstructions = createTimeline(jsPsych, { show_instructions: true }); + expect(timeline.timeline.length).toBeLessThan(timelineWithInstructions.timeline.length); + }); + + it("should create timeline without practice when disabled", () => { + const timeline = createTimeline(jsPsych, { show_practice: false }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should respect custom trial count", () => { + const customTrials = 15; + const timeline = createTimeline(jsPsych, { + num_trials: customTrials, + show_instructions: false, + show_practice: false + }); + + // Should have main trials plus completion screen + expect(timeline.timeline.length).toBeGreaterThanOrEqual(2); + }); + + it("should respect custom practice trial count", () => { + const customPractice = 6; + const timeline = createTimeline(jsPsych, { + num_practice: customPractice, + show_practice: true, + show_instructions: false + }); + + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should handle different stimuli types", () => { + const fishTimeline = createTimeline(jsPsych, { stimuli_type: 'fish' }); + const arrowTimeline = createTimeline(jsPsych, { stimuli_type: 'arrow' }); + const layeredTimeline = createTimeline(jsPsych, { stimuli_type: 'layered' }); + + expect(fishTimeline.timeline.length).toBeGreaterThan(0); + expect(arrowTimeline.timeline.length).toBeGreaterThan(0); + expect(layeredTimeline.timeline.length).toBeGreaterThan(0); + }); + + it("should handle SVG override parameter", () => { + const customSVGs = [ + '', + '' + ]; + + const timeline = createTimeline(jsPsych, { svg: customSVGs }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should handle custom stimuli", () => { + const customStimuli = { + left: ['test left'], + right: ['test right'] + }; + + const timeline = createTimeline(jsPsych, { custom_stimuli: customStimuli }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should set custom fixation duration", () => { + const customDuration = 1000; + const timeline = createTimeline(jsPsych, { fixation_duration: customDuration }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + }); + + describe("createFlankerStim", () => { + it("should create HTML for congruent left stimulus", () => { + const html = createFlankerStim('left', true); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should create HTML for incongruent right stimulus", () => { + const html = createFlankerStim('right', false); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should create HTML for congruent right stimulus", () => { + const html = createFlankerStim('right', true); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should create HTML for incongruent left stimulus", () => { + const html = createFlankerStim('left', false); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should handle custom stimuli arrays", () => { + const customStimuli = ['test']; + const html = createFlankerStim('left', true, customStimuli); + expect(html).toContain('flanker-stim'); + }); + + it("should handle custom stimuli objects", () => { + const customStimuli = { + left: ['left test'], + right: ['right test'] + }; + const html = createFlankerStim('left', true, customStimuli); + expect(html).toContain('flanker-stim'); + }); + }); + + describe("createPracticeStim", () => { + it("should create HTML for practice stimulus with highlighting", () => { + const html = createPracticeStim('left', true); + expect(html).toContain('flanker-stim practice'); + expect(html).toContain('center highlighted'); + }); + + it("should create different HTML than regular stimulus", () => { + const practiceHtml = createPracticeStim('left', true); + const regularHtml = createFlankerStim('left', true); + + expect(practiceHtml).toContain('practice'); + expect(practiceHtml).toContain('highlighted'); + expect(regularHtml).not.toContain('practice'); + expect(regularHtml).not.toContain('highlighted'); + }); + + it("should handle all direction and congruency combinations", () => { + const combinations = [ + ['left', true], + ['left', false], + ['right', true], + ['right', false] + ]; + + combinations.forEach(([direction, congruent]) => { + const html = createPracticeStim(direction as string, congruent as boolean); + expect(html).toContain('flanker-stim practice'); + expect(html).toContain('center highlighted'); + }); + }); + }); + + describe("calculatePerformance", () => { + it("should return zero performance for empty data", () => { + const performance = calculatePerformance([]); + expect(performance.accuracy).toBe(0); + expect(performance.mean_rt).toBe(0); + expect(performance.total_trials).toBe(0); + }); + + it("should calculate accuracy correctly", () => { + const mockData = [ + { task: 'flanker', correct: true, rt: 500 }, + { task: 'flanker', correct: false, rt: 600 }, + { task: 'flanker', correct: true, rt: 550 }, + { task: 'flanker', correct: true, rt: 450 } + ]; + + const performance = calculatePerformance(mockData); + expect(performance.accuracy).toBe(75); // 3/4 = 75% + expect(performance.total_trials).toBe(4); + expect(performance.correct_trials).toBe(3); + expect(performance.mean_rt).toBeCloseTo(500, 1); // Mean of correct trials: (500+550+450)/3 + }); + + it("should filter only flanker task data", () => { + const mockData = [ + { task: 'flanker', correct: true, rt: 500 }, + { task: 'other', correct: false, rt: 600 }, + { task: 'flanker', correct: true, rt: 550 } + ]; + + const performance = calculatePerformance(mockData); + expect(performance.total_trials).toBe(2); // Only flanker trials + expect(performance.accuracy).toBe(100); // Both flanker trials correct + }); + + it("should handle all incorrect trials", () => { + const mockData = [ + { task: 'flanker', correct: false, rt: 500 }, + { task: 'flanker', correct: false, rt: 600 } + ]; + + const performance = calculatePerformance(mockData); + expect(performance.accuracy).toBe(0); + expect(performance.mean_rt).toBe(0); // No correct trials + expect(performance.correct_trials).toBe(0); + }); + }); + + describe("Stimulus Constants", () => { + it("should provide default_stimuli with left and right", () => { + expect(default_stimuli).toBeDefined(); + expect(default_stimuli.left).toBeDefined(); + expect(default_stimuli.right).toBeDefined(); + expect(typeof default_stimuli.left).toBe('string'); + expect(typeof default_stimuli.right).toBe('string'); + }); + + it("should provide fish_stimuli with left and right", () => { + expect(fish_stimuli).toBeDefined(); + expect(fish_stimuli.left).toBeDefined(); + expect(fish_stimuli.right).toBeDefined(); + expect(typeof fish_stimuli.left).toBe('string'); + expect(typeof fish_stimuli.right).toBe('string'); + }); + + it("should provide arrow_stimuli with left and right", () => { + expect(arrow_stimuli).toBeDefined(); + expect(arrow_stimuli.left).toBeDefined(); + expect(arrow_stimuli.right).toBeDefined(); + expect(typeof arrow_stimuli.left).toBe('string'); + expect(typeof arrow_stimuli.right).toBe('string'); + }); + + it("should have different stimuli for left and right directions", () => { + expect(default_stimuli.left).not.toBe(default_stimuli.right); + expect(fish_stimuli.left).not.toBe(fish_stimuli.right); + expect(arrow_stimuli.left).not.toBe(arrow_stimuli.right); + }); + }); + + describe("Text and Instructions", () => { + it("should provide trial_text object", () => { + expect(trial_text).toBeDefined(); + expect(trial_text.continue_button).toBeDefined(); + expect(trial_text.left_button).toBeDefined(); + expect(trial_text.right_button).toBeDefined(); + expect(trial_text.fixation_cross).toBeDefined(); + expect(trial_text.correct_feedback).toBeDefined(); + expect(trial_text.incorrect_feedback).toBeDefined(); + }); + + it("should provide instruction_pages array", () => { + expect(instruction_pages).toBeDefined(); + expect(Array.isArray(instruction_pages)).toBe(true); + expect(instruction_pages.length).toBeGreaterThan(0); + }); + + it("should have properly structured instruction pages", () => { + instruction_pages.forEach((page, index) => { + expect(page).toBeDefined(); + expect(page.buttons).toBeDefined(); + expect(Array.isArray(page.buttons)).toBe(true); + }); + }); + }); + + describe("Timeline Units", () => { + it("should provide timelineUnits object", () => { + expect(timelineUnits).toBeDefined(); + expect(timelineUnits.instructions).toBeDefined(); + expect(timelineUnits.practice).toBeDefined(); + expect(timelineUnits.main).toBeDefined(); + expect(timelineUnits.completion).toBeDefined(); + }); + }); + + describe("Utils Export", () => { + it("should provide utils object with functions", () => { + expect(utils).toBeDefined(); + expect(utils.calculatePerformance).toBeDefined(); + expect(utils.createFlankerStim).toBeDefined(); + expect(utils.createPracticeStim).toBeDefined(); + expect(typeof utils.calculatePerformance).toBe('function'); + expect(typeof utils.createFlankerStim).toBe('function'); + expect(typeof utils.createPracticeStim).toBe('function'); + }); + + it("should have utils functions work the same as direct exports", () => { + const mockData = [{ task: 'flanker', correct: true, rt: 500 }]; + + const directResult = calculatePerformance(mockData); + const utilsResult = utils.calculatePerformance(mockData); + + expect(directResult).toEqual(utilsResult); + }); + }); + + describe("FlankerConfig Interface", () => { + it("should accept valid config objects", () => { + const configs: FlankerConfig[] = [ + {}, + { stimuli_type: 'fish' }, + { stimuli_type: 'arrow' }, + { stimuli_type: 'layered' }, + { fixation_duration: 1000 }, + { show_instructions: false }, + { show_practice: true }, + { num_practice: 10 }, + { num_trials: 25 }, + { svg: ['test'] }, + { custom_stimuli: { left: ['left'] } } + ]; + + configs.forEach(config => { + expect(() => createTimeline(jsPsych, config)).not.toThrow(); + }); + }); + }); + + describe("SVG Flipping Functionality", () => { + it("should generate different left and right stimuli from same input", () => { + // Test with a simple SVG array input + const testSVG = ['']; + + const leftHtml = createFlankerStim('left', true, testSVG); + const rightHtml = createFlankerStim('right', true, testSVG); + + // Should be different due to flipping + expect(leftHtml).not.toBe(rightHtml); + expect(leftHtml).toContain('flanker-stim'); + expect(rightHtml).toContain('flanker-stim'); + }); + + it("should handle SVGs with existing transforms", () => { + // Test with SVG that already has transform (like the fish) + const transformedSVG = ['']; + + const leftHtml = createFlankerStim('left', true, transformedSVG); + const rightHtml = createFlankerStim('right', true, transformedSVG); + + expect(leftHtml).not.toBe(rightHtml); + expect(leftHtml).toContain('flanker-stim'); + expect(rightHtml).toContain('flanker-stim'); + }); + }); + + describe("Layered SVG Functionality", () => { + it("should handle multiple SVG layers", () => { + const layeredSVGs = [ + '', + '' + ]; + + const html = createFlankerStim('left', true, layeredSVGs); + + expect(html).toContain('flanker-stim'); + expect(html).toContain('position: relative'); + }); + + it("should handle single SVG in array", () => { + const singleSVG = ['']; + + const html = createFlankerStim('left', true, singleSVG); + + expect(html).toContain('flanker-stim'); + }); + }); + + describe("Error Handling", () => { + it("should handle invalid direction gracefully", () => { + expect(() => createFlankerStim('invalid' as any, true)).not.toThrow(); + }); + + it("should handle undefined stimuli", () => { + expect(() => createFlankerStim('left', true, undefined as any)).not.toThrow(); + }); + + it("should handle empty stimuli array", () => { + expect(() => createFlankerStim('left', true, [])).not.toThrow(); + }); + + it("should handle malformed SVG strings", () => { + const badSVG = ['not an svg']; + expect(() => createFlankerStim('left', true, badSVG)).not.toThrow(); + }); + }); +}); \ No newline at end of file From f2638cac2971ed6c3042a7b03227c3fe725bc66e Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Fri, 27 Jun 2025 11:45:46 -0400 Subject: [PATCH 18/37] added stimuli_amount param to specify number of fishies --- packages/flanker-inhibitory/README.md | 66 ++++++- .../docs/flanker-inhibitory.md | 177 +++++++++++++++--- .../flanker-inhibitory/examples/index.html | 49 +++-- packages/flanker-inhibitory/package.json | 23 ++- packages/flanker-inhibitory/src/index.spec.ts | 106 +++++++++++ packages/flanker-inhibitory/src/index.ts | 54 ++++-- 6 files changed, 406 insertions(+), 69 deletions(-) diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md index 0c30270e..4bc53f3d 100644 --- a/packages/flanker-inhibitory/README.md +++ b/packages/flanker-inhibitory/README.md @@ -2,19 +2,19 @@ ## Overview -The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and +The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and selective attention based on the NIH Toolbox Flanker task. Participants respond to the direction of a central stimulus while ignoring flanking stimuli that may be congruent or incongruent with the target. ## Loading ### In browser ```html - ``` ### Via NPM -``` +```bash npm install @jspsych-timelines/flanker-inhibitory ``` @@ -31,20 +31,76 @@ import { createTimeline, timelineUnits, utils } from "@jspsych-timelines/flanker ### createTimeline #### jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { *options* }) ⇒ timeline -Description of the timeline that this plugin generates + +Creates a complete flanker task timeline with instructions, practice trials, and main task trials. The task uses layered fish and arrow stimuli by default, with configurable parameters for customization. The following parameters can be specified in the **options** parameter. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| parameter | type | default | description | +| stimuli_type | string | 'layered' | Type of stimuli to use: 'fish', 'arrow', or 'layered' | +| custom_stimuli | object | undefined | Custom stimuli object with left/right arrays | +| svg | array | undefined | Override parameter - array of SVGs to layer on top of each other | +| stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd ≥3) | +| fixation_duration | number | 500 | Duration of fixation cross in milliseconds | +| show_instructions | boolean | true | Whether to show instruction screens | +| show_practice | boolean | true | Whether to include practice trials | +| num_practice | number | 8 | Number of practice trials | +| num_trials | number | 20 | Number of main task trials | + +#### Example Usage +```js +const jsPsych = initJsPsych(); + +// Basic usage with defaults +const timeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych); + +// Custom configuration +const customTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + stimuli_type: 'fish', + stimuli_amount: 7, + num_trials: 40, + fixation_duration: 750 +}); + +// Using custom SVG override +const svgTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + svg: [ + '...base layer...', + '...overlay layer...' + ], + stimuli_amount: 9 +}); + +jsPsych.run([timeline]); +``` ### timelineUnits +Object containing identifiers for different phases of the task: +- `instructions`: Instruction screens +- `practice`: Practice trials +- `main`: Main task trials +- `completion`: Task completion screen ### utils +Utility functions for working with flanker stimuli and data: +- `calculatePerformance(data)`: Calculate accuracy and mean RT from trial data +- `createFlankerStim(direction, congruent, stimuli, stimuli_amount)`: Generate flanker stimulus HTML +- `createPracticeStim(direction, congruent, stimuli, stimuli_amount)`: Generate practice stimulus with highlighting + +## Features + +- **Adaptive stimuli**: Fish stimuli for younger children, arrows for older children, or layered combinations +- **Configurable difficulty**: Adjust number of flanker stimuli (3, 5, 7, 9, etc.) +- **Mobile-friendly**: Touch-optimized button interfaces +- **Practice mode**: Highlighted center stimulus for learning +- **Multiple input types**: Support for SVG arrays, custom stimuli objects, or simple overrides +- **Automatic flipping**: Single SVG automatically generates left/right facing versions +- **Layered SVGs**: Stack multiple SVG elements for complex stimuli + ## Author / Citation Hunter Farhat \ No newline at end of file diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md index 9bcc46a8..c6aa4865 100644 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -1,51 +1,178 @@ # flanker-inhibitory -The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and +The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and selective attention. Based on the NIH Toolbox Flanker task, participants must respond to the direction of a central stimulus while ignoring flanking stimuli that may be congruent (pointing the same direction) or incongruent (pointing the opposite direction). ## Parameters -### Initialization Parameters +### Configuration Parameters -Initialization parameters can be set when calling `initJsPsych()` +Configuration parameters can be passed when calling `createTimeline()`: ```js -initJsPsych({ - timelines: [ - {type: jsPsychTimelineFlankerInhibitory, params: {...}} - ] -}) +const timeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + stimuli_type: 'fish', + stimuli_amount: 7, + num_trials: 40 +}); ``` Parameter | Type | Default Value | Description ----------|------|---------------|------------ - | | | - -### Trial Parameters - -Trial parameters can be set when adding the timeline to a trial object. +stimuli_type | string | 'layered' | Type of stimuli: 'fish', 'arrow', or 'layered' (fish + arrow) +custom_stimuli | object | undefined | Custom stimuli object with left/right arrays of SVG strings +svg | array | undefined | Override parameter - array of SVG strings to layer on top of each other +stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd number ≥3) +fixation_duration | number | 500 | Duration of fixation cross display in milliseconds +show_instructions | boolean | true | Whether to display instruction screens before the task +show_practice | boolean | true | Whether to include practice trials with feedback +num_practice | number | 8 | Number of practice trials to present +num_trials | number | 20 | Number of main task trials to present + +### Stimulus Configuration Examples ```js -var trial = { - type: jsPsych..., - timelines: [ - {type: jsPsychTimelineFlankerInhibitory, params: {...}} - ] -} +// Use fish stimuli only +const fishConfig = { stimuli_type: 'fish' }; + +// Use custom SVG with 7 total stimuli +const customConfig = { + svg: ['...custom svg...'], + stimuli_amount: 7 +}; + +// Use layered stimuli with custom amounts +const layeredConfig = { + stimuli_type: 'layered', + stimuli_amount: 9, + custom_stimuli: { + left: ['...left base...', '...left overlay...'], + right: ['...right base...', '...right overlay...'] + } +}; ``` -Parameter | Type | Default Value | Description -----------|------|---------------|------------ - | | | +### Timeline Structure + +The complete timeline includes: + +1. **Instructions** (if `show_instructions: true`) + - Multi-page instructions explaining the task + - Examples of congruent and incongruent trials + - Strategy tips for performance + +2. **Practice Trials** (if `show_practice: true`) + - Highlighted center stimulus to aid learning + - Immediate feedback after each response + - Performance summary at completion + +3. **Main Task** + - Equal numbers of congruent and incongruent trials + - Left and right direction trials + - Randomized trial order + +4. **Completion Screen** + - Final performance summary + - Task completion confirmation ## Data Generated +Each trial generates the following data: + Name | Type | Value -----|------|------ - | | +task | string | 'flanker' for stimulus trials, 'fixation' for fixation periods +phase | string | 'practice' or 'main' depending on trial phase +direction | string | 'left' or 'right' - direction of center stimulus +congruent | boolean | true if flankers match center direction, false if opposite +response | number | 0 for left button, 1 for right button +rt | number | Response time in milliseconds +correct | boolean | true if response matches stimulus direction +trial_number | number | Sequential trial number within phase + +### Performance Calculation + +The `calculatePerformance()` utility function returns: + +```js +{ + accuracy: 85.5, // Percentage of correct responses + mean_rt: 650, // Mean response time for correct trials (ms) + total_trials: 20, // Total number of trials analyzed + correct_trials: 17 // Number of correct responses +} +``` ## Functions -If the timeline adds any static functions, list them here. +### createTimeline(jsPsych, config) + +Creates the complete flanker task timeline with the specified configuration. + +**Parameters:** +- `jsPsych`: jsPsych instance +- `config`: Configuration object (see Parameters section) + +**Returns:** Timeline object ready for use with `jsPsych.run()` + +### calculatePerformance(data) + +Analyzes trial data to compute performance metrics. + +**Parameters:** +- `data`: Array of trial data objects + +**Returns:** Performance object with accuracy, mean RT, and trial counts + +### createFlankerStim(direction, congruent, stimuli, stimuli_amount) + +Generates HTML for flanker stimulus display. + +**Parameters:** +- `direction`: 'left' or 'right' +- `congruent`: boolean indicating trial type +- `stimuli`: Stimuli object or array +- `stimuli_amount`: Number of stimuli to display (default: 5) + +**Returns:** HTML string for stimulus display + +### createPracticeStim(direction, congruent, stimuli, stimuli_amount) + +Generates HTML for practice stimulus with center highlighting. + +**Parameters:** Same as `createFlankerStim()` + +**Returns:** HTML string with highlighted center stimulus + +## Stimulus System + +### Layered SVGs + +The package supports layering multiple SVG elements to create complex stimuli: + +```js +const layeredStimuli = [ + '...base fish shape...', + '...arrow overlay...', + '...additional elements...' +]; +``` + +### Automatic Flipping + +Single SVGs are automatically flipped to create left/right facing versions: + +```js +// Provide only right-facing SVG +const config = { + svg: ['...right-facing fish...'] +}; +// Left-facing version is automatically generated +``` + +### Mobile Optimization -### function() +- Touch-friendly button interfaces +- Responsive sizing using vmin units +- Optimized for various screen sizes +- No keyboard input required diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index 396afcb5..f2c77d01 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -36,41 +36,54 @@ // Configure the task const config = { // Stimulus options - stimuli_type: 'fish', // 'fish', 'arrow', or 'layered' (fish + arrow, can be changed in stimuli.ts) + stimuli_type: 'layered', // 'fish', 'arrow', or 'layered' (fish + arrow overlay) + stimuli_amount: 8, // Number of stimuli in flanker display (3, 5, 7, 9, etc. - must be odd) // SVG override - uncomment to use custom SVG array // svg: [ - // '', + // '', // '' // ], - // Trial configuration - num_practice_trials: 4, // Reduced for demo - num_congruent_trials: 8, // Reduced for demo - num_incongruent_trials: 4, // Reduced for demo + // Trial configuration + num_practice: 6, // Number of practice trials + num_trials: 20, // Number of main task trials // Timing options (in milliseconds) fixation_duration: 500, - response_timeout: 5000, - feedback_duration: 1000, - inter_trial_interval: 1000, // Task flow options show_instructions: true, - show_practice: true, - show_feedback: true, - - // Performance thresholds - practice_threshold: 50, // Reduced for demo - minimum accuracy to pass practice - fish_threshold_for_arrows: 70, // Reduced for demo - accuracy needed to advance to arrow block - - // Progression options - auto_advance_to_arrows: true, // Automatically show arrow block if fish performance is good + show_practice: true }; // Create the timeline const task = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, config); + // Alternative configurations (uncomment to try): + + // Simple fish-only with 3 stimuli + // const simpleFish = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + // stimuli_type: 'fish', + // stimuli_amount: 3, + // num_practice: 4, + // num_trials: 12 + // }); + + // Complex arrow display with 9 stimuli + // const complexArrows = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + // stimuli_type: 'arrow', + // stimuli_amount: 9, + // num_practice: 8, + // num_trials: 40 + // }); + + // Custom SVG override + // const customSVG = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + // svg: [''], + // stimuli_amount: 5 + // }); + // Run the experiment jsPsych.run([task]); diff --git a/packages/flanker-inhibitory/package.json b/packages/flanker-inhibitory/package.json index 4590ff3f..b032ee44 100644 --- a/packages/flanker-inhibitory/package.json +++ b/packages/flanker-inhibitory/package.json @@ -1,31 +1,36 @@ { "name": "@jspsych-timelines/flanker-inhibitory", "version": "0.0.1", - "description": "The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and", + "description": "Flanker Inhibitory Control and Attention test timeline for jsPsych with configurable fish/arrow stimuli and layered SVG support", "type": "module", - "main": "dist/index.mjs", + "main": "dist/index.js", "types": "dist/index.d.ts", - "unpkg": "dist/index.browser.min.js", + "unpkg": "dist/index.global.js", "scripts": { "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineFlankerInhibitory" }, "repository": { "type": "git", - "url": "https://github.com/farhat60/jspsych-timelines/", - "directory": "packages" + "url": "git+https://github.com/jspsych/jspsych-timelines.git", + "directory": "packages/flanker-inhibitory" }, "keywords": [ - "jsPsych" + "jsPsych", + "flanker", + "inhibitory control", + "attention", + "NIH Toolbox", + "cognitive assessment" ], "author": { "name": "Hunter Farhat", - "url": "farhat60" + "url": "https://github.com/farhat60" }, "license": "MIT", "bugs": { - "url": "https://github.com/farhat60/jspsych-timelines//issues" + "url": "https://github.com/jspsych/jspsych-timelines/issues" }, - "homepage": "https://github.com/jspsych/jspsych-timelines/packages/flanker-inhibitory/README.md", + "homepage": "https://github.com/jspsych/jspsych-timelines/tree/main/packages/flanker-inhibitory#readme", "devDependencies": { "jspsych": "^8.0.0", "@jspsych/config": "^3.2.2", diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts index 0e1e2283..609f66a5 100644 --- a/packages/flanker-inhibitory/src/index.spec.ts +++ b/packages/flanker-inhibitory/src/index.spec.ts @@ -13,6 +13,7 @@ import { instruction_pages, FlankerConfig } from "./index"; +import { layered_stimuli } from "./stimuli"; describe("Flanker Inhibitory Control Task", () => { let jsPsych: any; @@ -389,6 +390,99 @@ describe("Flanker Inhibitory Control Task", () => { }); }); + describe("Stimuli Amount Functionality", () => { + // Helper function to count only top-level spans (flanker positions) + const countFlankerPositions = (html: string): number => { + // Count spans that are direct children of the flanker-stim div + // These represent the actual flanker positions, not inner SVG content + const matches = html.match(/]*>/g) || []; + return matches.length; + }; + + it("should default to 5 stimuli (2 left + 1 center + 2 right)", () => { + const html = createFlankerStim('left', true); + const spanCount = countFlankerPositions(html); + expect(spanCount).toBe(5); + }); + + it("should create correct number of stimuli for custom amounts", () => { + const testAmounts = [3, 5, 7, 9]; + + testAmounts.forEach(amount => { + const html = createFlankerStim('left', true, layered_stimuli, amount); + const spanCount = countFlankerPositions(html); + expect(spanCount).toBe(amount); + }); + }); + + it("should handle even numbers by rounding up to next odd", () => { + const html4 = createFlankerStim('left', true, layered_stimuli, 4); + const html6 = createFlankerStim('left', true, layered_stimuli, 6); + const html8 = createFlankerStim('left', true, layered_stimuli, 8); + + expect(countFlankerPositions(html4)).toBe(5); // 4 -> 5 + expect(countFlankerPositions(html6)).toBe(7); // 6 -> 7 + expect(countFlankerPositions(html8)).toBe(9); // 8 -> 9 + }); + + it("should enforce minimum of 3 stimuli", () => { + const html1 = createFlankerStim('left', true, layered_stimuli, 1); + const html2 = createFlankerStim('left', true, layered_stimuli, 2); + const htmlNegative = createFlankerStim('left', true, layered_stimuli, -5); + + expect(countFlankerPositions(html1)).toBe(3); + expect(countFlankerPositions(html2)).toBe(3); + expect(countFlankerPositions(htmlNegative)).toBe(3); + }); + + it("should maintain center stimulus with highlighting in practice", () => { + const html = createPracticeStim('left', true, layered_stimuli, 7); + const spanCount = countFlankerPositions(html); + expect(spanCount).toBe(7); + expect(html).toContain('center highlighted'); + }); + + it("should work with different stimuli types", () => { + const fishHtml = createFlankerStim('left', true, fish_stimuli, 7); + const arrowHtml = createFlankerStim('left', true, arrow_stimuli, 7); + + expect(countFlankerPositions(fishHtml)).toBe(7); + expect(countFlankerPositions(arrowHtml)).toBe(7); + }); + + it("should be configurable via timeline config", () => { + const timeline = createTimeline(jsPsych, { + stimuli_amount: 7, + show_instructions: false, + show_practice: false + }); + + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should validate stimuli_amount in different function calls", () => { + // Test with simple SVGs (no inner spans) + const testSVGs = ['']; + const html1 = createFlankerStim('left', true, testSVGs, 9); + expect(countFlankerPositions(html1)).toBe(9); + + // Test with objects + const testObj = { left: [''], right: [''] }; + const html2 = createFlankerStim('left', true, testObj, 7); + expect(countFlankerPositions(html2)).toBe(7); + }); + + it("should handle invalid amounts by using valid fallbacks", () => { + // NaN should fall back to default (5) + const htmlNaN = createFlankerStim('left', true, layered_stimuli, NaN); + expect(countFlankerPositions(htmlNaN)).toBe(5); + + // Very large numbers should still work + const htmlLarge = createFlankerStim('left', true, layered_stimuli, 15); + expect(countFlankerPositions(htmlLarge)).toBe(15); + }); + }); + describe("Error Handling", () => { it("should handle invalid direction gracefully", () => { expect(() => createFlankerStim('invalid' as any, true)).not.toThrow(); @@ -406,5 +500,17 @@ describe("Flanker Inhibitory Control Task", () => { const badSVG = ['not an svg']; expect(() => createFlankerStim('left', true, badSVG)).not.toThrow(); }); + + it("should handle invalid stimuli_amount gracefully", () => { + expect(() => createFlankerStim('left', true, layered_stimuli, NaN)).not.toThrow(); + expect(() => createFlankerStim('left', true, layered_stimuli, Infinity)).not.toThrow(); + + // Should fall back to default value (5) for invalid inputs + const htmlNaN = createFlankerStim('left', true, layered_stimuli, NaN); + const htmlInfinity = createFlankerStim('left', true, layered_stimuli, Infinity); + + expect((htmlNaN.match(/]*>/g) || []).length).toBe(5); + expect((htmlInfinity.match(/]*>/g) || []).length).toBe(5); + }); }); }); \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index e3881ab1..c87b17cc 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -142,7 +142,12 @@ export const arrow_stimuli = processLayeredStimuli(arrow_only); // STIMULUS CREATION FUNCTIONS // ============================================================================ -export function createFlankerStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli) { +export function createFlankerStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { + // Ensure stimuli_amount is valid (odd number ≥3) + let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; + const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); + const halfFlankers = Math.floor(validAmount / 2); + // Handle different input types let processedStimuli; if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { @@ -155,16 +160,30 @@ export function createFlankerStim(direction, congruent, stimuli: string[] | { le const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; let html = `

      `; - html += `${flanker}`; - html += `${flanker}`; + + // Add left flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + // Add center stimulus html += `${center}`; - html += `${flanker}`; - html += `${flanker}`; + + // Add right flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + html += `
      `; return html; } -export function createPracticeStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli) { +export function createPracticeStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { + // Ensure stimuli_amount is valid (odd number ≥3) + let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; + const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); + const halfFlankers = Math.floor(validAmount / 2); + // Handle different input types let processedStimuli; if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { @@ -177,11 +196,20 @@ export function createPracticeStim(direction, congruent, stimuli: string[] | { l const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; let html = `
      `; - html += `${flanker}`; - html += `${flanker}`; + + // Add left flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + // Add center stimulus with highlighting html += `${center}`; - html += `${flanker}`; - html += `${flanker}`; + + // Add right flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + html += `
      `; return html; } @@ -194,6 +222,7 @@ export interface FlankerConfig { stimuli_type?: 'fish' | 'arrow' | 'layered'; custom_stimuli?: { left?: string[]; right?: string[] }; svg?: string[]; // Override parameter - array of SVGs to layer + stimuli_amount?: number; // Number of stimuli in flanker display (default: 5, must be odd ≥3) fixation_duration?: number; show_instructions?: boolean; show_practice?: boolean; @@ -235,6 +264,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { stimuli_type = 'layered', custom_stimuli, svg, + stimuli_amount = 5, fixation_duration = 500, show_instructions = true, show_practice = true, @@ -332,7 +362,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { return `
      ${trial_text.main_task_prompt}
      - ${createPracticeStim(direction, congruent, stimuli)} + ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)}
      `; }, @@ -431,7 +461,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { return `
      ${trial_text.main_task_prompt}
      - ${createFlankerStim(direction, congruent, stimuli)} + ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)}
      `; }, From 5465718ae219785e48d675aa02be0d5669f9b87b Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Fri, 27 Jun 2025 12:14:36 -0400 Subject: [PATCH 19/37] removed all hardcoded text to text.ts --- packages/flanker-inhibitory/README.md | 11 ++-- .../docs/flanker-inhibitory.md | 7 +-- .../flanker-inhibitory/examples/index.html | 51 ++++++++++++++++++- packages/flanker-inhibitory/src/index.ts | 12 ++--- packages/flanker-inhibitory/src/styles.css | 8 ++- packages/flanker-inhibitory/src/text.ts | 9 +++- 6 files changed, 80 insertions(+), 18 deletions(-) diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md index 4bc53f3d..784e06fd 100644 --- a/packages/flanker-inhibitory/README.md +++ b/packages/flanker-inhibitory/README.md @@ -40,7 +40,7 @@ The following parameters can be specified in the **options** parameter. |-----------|------|---------|-------------| | stimuli_type | string | 'layered' | Type of stimuli to use: 'fish', 'arrow', or 'layered' | | custom_stimuli | object | undefined | Custom stimuli object with left/right arrays | -| svg | array | undefined | Override parameter - array of SVGs to layer on top of each other | +| svg | array | undefined | Override parameter - array of SVGs to layer (should be right-facing; left version auto-generated) | | stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd ≥3) | | fixation_duration | number | 500 | Duration of fixation cross in milliseconds | | show_instructions | boolean | true | Whether to show instruction screens | @@ -64,13 +64,14 @@ const customTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, fixation_duration: 750 }); -// Using custom SVG override +// Using custom SVG override (provide right-facing SVGs) const svgTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { svg: [ - '...base layer...', - '...overlay layer...' + '...right-facing base layer...', + '...right-facing overlay layer...' ], stimuli_amount: 9 + // Left-facing versions will be automatically generated by flipping }); jsPsych.run([timeline]); @@ -98,7 +99,7 @@ Utility functions for working with flanker stimuli and data: - **Mobile-friendly**: Touch-optimized button interfaces - **Practice mode**: Highlighted center stimulus for learning - **Multiple input types**: Support for SVG arrays, custom stimuli objects, or simple overrides -- **Automatic flipping**: Single SVG automatically generates left/right facing versions +- **Automatic flipping**: Single right-facing SVG automatically generates left-facing version - **Layered SVGs**: Stack multiple SVG elements for complex stimuli ## Author / Citation diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md index c6aa4865..c6834b64 100644 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -20,7 +20,7 @@ Parameter | Type | Default Value | Description ----------|------|---------------|------------ stimuli_type | string | 'layered' | Type of stimuli: 'fish', 'arrow', or 'layered' (fish + arrow) custom_stimuli | object | undefined | Custom stimuli object with left/right arrays of SVG strings -svg | array | undefined | Override parameter - array of SVG strings to layer on top of each other +svg | array | undefined | Override parameter - array of right-facing SVG strings to layer (left version auto-generated) stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd number ≥3) fixation_duration | number | 500 | Duration of fixation cross display in milliseconds show_instructions | boolean | true | Whether to display instruction screens before the task @@ -34,10 +34,11 @@ num_trials | number | 20 | Number of main task trials to present // Use fish stimuli only const fishConfig = { stimuli_type: 'fish' }; -// Use custom SVG with 7 total stimuli +// Use custom right-facing SVG with 7 total stimuli const customConfig = { - svg: ['...custom svg...'], + svg: ['...right-facing custom svg...'], stimuli_amount: 7 + // Left-facing version automatically generated }; // Use layered stimuli with custom amounts diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index f2c77d01..933aebce 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -78,12 +78,61 @@ // num_trials: 40 // }); - // Custom SVG override + // Custom SVG override (simple array) // const customSVG = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { // svg: [''], // stimuli_amount: 5 // }); + // Custom stimuli with car example (left auto-generated from right) + // const carStimuli = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + // custom_stimuli: { + // right: [ + // // Custom car pointing right + // '' + + // '' + + // '' + + // '' + + // '' + + // '' + // ] + // // Left version will be auto-generated by flipping + // }, + // stimuli_amount: 5, + // num_practice: 4, + // num_trials: 16 + // }); + + // Custom stimuli with different left/right designs + // const asymmetricStimuli = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + // custom_stimuli: { + // left: [ + // '' + + // '' + + // '' + // ], + // right: [ + // '' + + // '' + + // '' + // ] + // }, + // stimuli_amount: 7 + // }); + + // Layered custom stimuli (multiple SVG layers) + // const layeredCustom = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + // custom_stimuli: { + // right: [ + // // Base layer - circle + // '', + // // Overlay layer - arrow + // '' + // ] + // }, + // stimuli_amount: 7 + // }); + // Run the experiment jsPsych.run([task]); diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index c87b17cc..781ff4b5 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -410,7 +410,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) {

      ${trial_text.practice_complete_header}

      ${trial_text.practice_complete_message}

      -

      Your accuracy: ${performance.accuracy.toFixed(1)}%

      +

      ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

      `; }, @@ -424,8 +424,8 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { type: jsPsychHtmlButtonResponse, stimulus: `
      -

      Main Task

      -

      Now we'll start the real task. Remember to focus on the middle fish!

      +

      ${trial_text.main_task_header}

      +

      ${trial_text.main_task_intro}

      `, choices: [trial_text.ready_button], @@ -497,9 +497,9 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) {

      ${trial_text.task_complete_header}

      ${trial_text.task_complete_message}

      -

      Your Performance:

      -

      Accuracy: ${performance.accuracy.toFixed(1)}%

      -

      Average Response Time: ${performance.mean_rt.toFixed(0)}ms

      +

      ${trial_text.performance_title}

      +

      ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

      +

      ${trial_text.response_time_label} ${performance.mean_rt.toFixed(0)}ms

`; diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css index 224e5b35..bd875127 100644 --- a/packages/flanker-inhibitory/src/styles.css +++ b/packages/flanker-inhibitory/src/styles.css @@ -6,7 +6,7 @@ max-height: 85vh; margin: 0 auto; padding: 3vmin; - text-align: left; + text-align: center; line-height: 1.4; background-color: white; border-radius: 2vmin; @@ -26,12 +26,14 @@ margin-top: 3vmin; margin-bottom: 1.5vmin; font-size: 3.5vmin; + text-align: center; } .flanker-instructions p { color: #555; margin-bottom: 1.5vmin; font-size: 3vmin; + text-align: center; } .flanker-instructions .performance-note { @@ -60,6 +62,7 @@ margin: 1vmin 0; font-size: 3vmin; color: #555; + text-align: center; } /* Performance summary */ @@ -69,7 +72,7 @@ border-radius: 2vmin; padding: 4vmin; margin: 4vmin 0; - text-align: left; + text-align: center; } .performance-summary h3 { @@ -83,6 +86,7 @@ margin: 2vmin 0; font-size: 3.5vmin; color: #6c757d; + text-align: center; } /* Trial display */ diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts index eafaeb9a..53e15061 100644 --- a/packages/flanker-inhibitory/src/text.ts +++ b/packages/flanker-inhibitory/src/text.ts @@ -23,9 +23,16 @@ export const trial_text = { practice_complete_message: "Great job! Now you're ready for the real task.", practice_failed_message: "Let's try the practice again. Remember to focus on the middle fish only.", - // Main task instructions + // Main task instructions and headers + main_task_header: "Main Task", + main_task_intro: "Now we'll start the real task. Remember to focus on the middle fish!", main_task_prompt: "Look at the middle fish. Click the button for the direction it's swimming.", + // Performance summary labels + performance_title: "Your Performance:", + accuracy_label: "Accuracy:", + response_time_label: "Average Response Time:", + // Fixation and inter-trial fixation_cross: "+", From d9cd741572b6b4e2da0243cd6cedf6339924e137 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Fri, 27 Jun 2025 13:32:31 -0400 Subject: [PATCH 20/37] Added TTS, responsive stimulus sizing, more tests... sizing a little buggy still. --- packages/flanker-inhibitory/README.md | 22 + .../docs/flanker-inhibitory.md | 62 ++ .../flanker-inhibitory/examples/index.html | 101 +-- packages/flanker-inhibitory/src/index.spec.ts | 217 +++++ packages/flanker-inhibitory/src/index.ts | 718 ++++++++++------ packages/flanker-inhibitory/src/index3.ts | 796 ++++++++++++++++++ .../flanker-inhibitory/src/stimuli copy.ts | 43 + packages/flanker-inhibitory/src/styles.css | 80 +- packages/flanker-inhibitory/src/text.ts | 53 ++ packages/flanker-inhibitory/test-alignment.js | 78 ++ 10 files changed, 1851 insertions(+), 319 deletions(-) create mode 100644 packages/flanker-inhibitory/src/index3.ts create mode 100644 packages/flanker-inhibitory/src/stimuli copy.ts create mode 100644 packages/flanker-inhibitory/test-alignment.js diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md index 784e06fd..d6d30d1d 100644 --- a/packages/flanker-inhibitory/README.md +++ b/packages/flanker-inhibitory/README.md @@ -47,6 +47,16 @@ The following parameters can be specified in the **options** parameter. | show_practice | boolean | true | Whether to include practice trials | | num_practice | number | 8 | Number of practice trials | | num_trials | number | 20 | Number of main task trials | +| enable_tts | boolean | false | Enable text-to-speech functionality | +| tts_voice | string | '' | Voice name for TTS (empty uses default) | +| tts_rate | number | 1.0 | Speech rate (0.1 to 10) | +| tts_pitch | number | 1.0 | Speech pitch (0 to 2) | +| tts_volume | number | 1.0 | Speech volume (0 to 1) | +| tts_lang | string | 'en-US' | Language code for TTS | +| speak_instructions | boolean | true | Speak instruction page content | +| speak_prompts | boolean | true | Speak trial prompts | +| speak_feedback | boolean | true | Speak feedback messages | +| auto_speak | boolean | true | Auto-start speaking when content loads | #### Example Usage @@ -74,6 +84,16 @@ const svgTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { // Left-facing versions will be automatically generated by flipping }); +// With TTS enabled +const ttsTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + enable_tts: true, + speak_instructions: true, + speak_prompts: true, + speak_feedback: true, + tts_rate: 0.9, + tts_lang: 'en-US' +}); + jsPsych.run([timeline]); ``` @@ -101,6 +121,8 @@ Utility functions for working with flanker stimuli and data: - **Multiple input types**: Support for SVG arrays, custom stimuli objects, or simple overrides - **Automatic flipping**: Single right-facing SVG automatically generates left-facing version - **Layered SVGs**: Stack multiple SVG elements for complex stimuli +- **Text-to-Speech**: Optional TTS support for accessibility and younger participants +- **Speech controls**: Space bar to skip speech, configurable voice settings ## Author / Citation diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md index c6834b64..7909cd34 100644 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -27,6 +27,16 @@ show_instructions | boolean | true | Whether to display instruction screens befo show_practice | boolean | true | Whether to include practice trials with feedback num_practice | number | 8 | Number of practice trials to present num_trials | number | 20 | Number of main task trials to present +enable_tts | boolean | false | Enable text-to-speech functionality +tts_voice | string | '' | Voice name for TTS (empty string uses default system voice) +tts_rate | number | 1.0 | Speech rate (0.1 to 10, where 1.0 is normal speed) +tts_pitch | number | 1.0 | Speech pitch (0 to 2, where 1.0 is normal pitch) +tts_volume | number | 1.0 | Speech volume (0 to 1, where 1.0 is maximum volume) +tts_lang | string | 'en-US' | Language code for speech synthesis +speak_instructions | boolean | true | Whether to speak instruction page content aloud +speak_prompts | boolean | true | Whether to speak trial prompts aloud +speak_feedback | boolean | true | Whether to speak feedback messages aloud +auto_speak | boolean | true | Whether to automatically start speaking when content loads ### Stimulus Configuration Examples @@ -50,6 +60,17 @@ const layeredConfig = { right: ['...right base...', '...right overlay...'] } }; + +// Enable TTS with custom voice settings +const ttsConfig = { + enable_tts: true, + speak_instructions: true, + speak_prompts: true, + speak_feedback: true, + tts_rate: 0.8, + tts_pitch: 1.1, + tts_lang: 'en-US' +}; ``` ### Timeline Structure @@ -177,3 +198,44 @@ const config = { - Optimized for various screen sizes - No keyboard input required +### Text-to-Speech (TTS) Support + +The package includes comprehensive TTS functionality for accessibility: + +#### Features +- **Configurable voice settings**: Rate, pitch, volume, and language +- **Selective content**: Choose what content to speak (instructions, prompts, feedback) +- **Speech controls**: Space bar to skip current speech +- **Auto-play options**: Automatically start speaking when content loads +- **Browser compatibility**: Uses Web Speech API (available in most modern browsers) + +#### TTS Configuration +```js +const config = { + enable_tts: true, + tts_voice: 'Google US English', // Specific voice name (optional) + tts_rate: 0.9, // Slightly slower than normal + tts_pitch: 1.0, // Normal pitch + tts_volume: 0.8, // 80% volume + tts_lang: 'en-US', // Language code + speak_instructions: true, + speak_prompts: true, + speak_feedback: true, + auto_speak: true +}; +``` + +#### Speech Controls +- **Space bar**: Skip current speech +- **Automatic cleanup**: Speech stops when task completes +- **Error handling**: Graceful fallback if TTS not supported + +#### Browser Support +TTS requires the Web Speech API, supported in: +- Chrome/Chromium browsers +- Safari (desktop and mobile) +- Edge +- Firefox (limited support) + +If TTS is not supported, the task continues normally without speech. + diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index 933aebce..d2270722 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -29,79 +29,62 @@ console.log('Task Performance:', performance); // Optional: display CSV data - // jsPsych.data.displayData(); + jsPsych.data.displayData(); } }); - // Configure the task + // Configure the task with ALL available parameters set to their defaults const config = { - // Stimulus options - stimuli_type: 'layered', // 'fish', 'arrow', or 'layered' (fish + arrow overlay) - stimuli_amount: 8, // Number of stimuli in flanker display (3, 5, 7, 9, etc. - must be odd) + // ===== STIMULUS CONFIGURATION ===== + stimuli_type: 'fish', // 'fish', 'arrow', or 'layered' (fish + arrow overlay) + custom_stimuli: undefined, // Custom stimuli object with left/right arrays, example at end of this file + svg: undefined, // Override parameter - array of SVGs to layer (should be right-facing) + stimuli_amount: 10, // Number of stimuli in flanker display (must be odd ≥3) - // SVG override - uncomment to use custom SVG array - // svg: [ - // '', - // '' - // ], + // ===== TIMING CONFIGURATION ===== + fixation_duration: 500, // Duration of fixation cross in milliseconds - // Trial configuration - num_practice: 6, // Number of practice trials + // ===== TASK FLOW CONFIGURATION ===== + show_instructions: false, // Whether to show instruction screens + show_practice: false, // Whether to include practice trials + num_practice: 2, // Number of practice trials num_trials: 20, // Number of main task trials - // Timing options (in milliseconds) - fixation_duration: 500, - - // Task flow options - show_instructions: true, - show_practice: true + // ===== TEXT-TO-SPEECH CONFIGURATION ===== + enable_tts: true, // Enable text-to-speech functionality + tts_voice: '', // Voice name for TTS (empty uses default) + tts_rate: 1.0, // Speech rate (0.1 to 10) + tts_pitch: 1.0, // Speech pitch (0 to 2) + tts_volume: 1.0, // Speech volume (0 to 1) + tts_lang: 'en-US', // Language code for TTS + speak_instructions: true, // Speak instruction page content + speak_prompts: false, // Speak trial prompts + speak_feedback: false, // Speak feedback messages + auto_speak: true, // Auto-start speaking when content loads }; // Create the timeline const task = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, config); - // Alternative configurations (uncomment to try): - - // Simple fish-only with 3 stimuli - // const simpleFish = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - // stimuli_type: 'fish', - // stimuli_amount: 3, - // num_practice: 4, - // num_trials: 12 - // }); - - // Complex arrow display with 9 stimuli - // const complexArrows = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - // stimuli_type: 'arrow', - // stimuli_amount: 9, - // num_practice: 8, - // num_trials: 40 - // }); - - // Custom SVG override (simple array) - // const customSVG = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - // svg: [''], - // stimuli_amount: 5 - // }); - // Custom stimuli with car example (left auto-generated from right) - // const carStimuli = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - // custom_stimuli: { - // right: [ - // // Custom car pointing right - // '' + - // '' + - // '' + - // '' + - // '' + - // '' - // ] - // // Left version will be auto-generated by flipping - // }, - // stimuli_amount: 5, - // num_practice: 4, - // num_trials: 16 - // }); + const task1 = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + + custom_stimuli: { + right: [ + // Custom car pointing right + '' + + '' + + '' + + '' + + '' + + '' + ] + // Left version will be auto-generated by flipping + }, + stimuli_amount: 5, + num_practice: 0, + num_trials: 16 + }); // Custom stimuli with different left/right designs // const asymmetricStimuli = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts index 609f66a5..586faebe 100644 --- a/packages/flanker-inhibitory/src/index.spec.ts +++ b/packages/flanker-inhibitory/src/index.spec.ts @@ -366,6 +366,60 @@ describe("Flanker Inhibitory Control Task", () => { expect(leftHtml).toContain('flanker-stim'); expect(rightHtml).toContain('flanker-stim'); }); + + it("should properly handle fish SVG alignment - no double transforms", () => { + // Test specifically with fish stimuli to ensure alignment fix works + const fishLeftHtml = createFlankerStim('left', true, fish_stimuli); + const fishRightHtml = createFlankerStim('right', true, fish_stimuli); + + // Both should contain flanker stimulus + expect(fishLeftHtml).toContain('flanker-stim'); + expect(fishRightHtml).toContain('flanker-stim'); + + // They should be different (one flipped) + expect(fishLeftHtml).not.toBe(fishRightHtml); + + // Test that the fish SVG original has a transform + expect(fish_stimuli.right).toContain('transform='); + + // Test that flipping twice gets back to original orientation + // This validates the fix for double-transform issues + const twice_flipped_left = createFlankerStim('left', true, fish_stimuli); + const twice_flipped_right = createFlankerStim('right', true, fish_stimuli); + expect(twice_flipped_left).not.toBe(twice_flipped_right); + }); + + it("should handle fish incongruent stimuli alignment correctly", () => { + // Test incongruent fish (center different from flankers) - key alignment test + const leftIncongruent = createFlankerStim('left', false, fish_stimuli); + const rightIncongruent = createFlankerStim('right', false, fish_stimuli); + + // Should produce valid HTML + expect(leftIncongruent).toContain('flanker-stim'); + expect(rightIncongruent).toContain('flanker-stim'); + + // Should be different + expect(leftIncongruent).not.toBe(rightIncongruent); + + // The key test: each should contain both orientations (center vs flankers) + // This is where alignment issues would be most visible + expect(leftIncongruent).toContain(''); + expect(rightIncongruent).toContain(''); + }); + + it("should maintain consistent alignment with layered fish+arrow stimuli", () => { + // Test the default layered stimuli (fish + arrow) for alignment + const leftLayered = createFlankerStim('left', true, default_stimuli); + const rightLayered = createFlankerStim('right', true, default_stimuli); + + expect(leftLayered).toContain('flanker-stim'); + expect(rightLayered).toContain('flanker-stim'); + expect(leftLayered).not.toBe(rightLayered); + + // Should contain position styling for layered elements + expect(leftLayered).toContain('position:'); + expect(rightLayered).toContain('position:'); + }); }); describe("Layered SVG Functionality", () => { @@ -513,4 +567,167 @@ describe("Flanker Inhibitory Control Task", () => { expect((htmlInfinity.match(/]*>/g) || []).length).toBe(5); }); }); + + describe("Edge Cases and Timeline Variable Errors", () => { + it("should handle num_practice=0 with show_practice=true without errors", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: 0, + show_practice: true, + show_instructions: false, + num_trials: 1 + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle num_trials=0 without errors", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_trials: 0, + show_instructions: false, + show_practice: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle both num_practice=0 and num_trials=0 without errors", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: 0, + show_practice: true, + num_trials: 0, + show_instructions: false + }); + // Should still have completion screen + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle negative numbers for practice trials", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: -5, + show_practice: true, + show_instructions: false, + num_trials: 1 + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle negative numbers for main trials", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_trials: -10, + show_instructions: false, + show_practice: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle very large numbers without breaking", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: 1000, + show_practice: true, + num_trials: 1000, + show_instructions: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle NaN values for trial counts", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: NaN, + show_practice: true, + num_trials: NaN, + show_instructions: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle all combinations of false/0 settings", () => { + const edgeCaseConfigs = [ + { show_instructions: false, show_practice: false, num_practice: 0, num_trials: 0 }, + { show_instructions: true, show_practice: false, num_practice: 0, num_trials: 0 }, + { show_instructions: false, show_practice: true, num_practice: 0, num_trials: 0 }, + { show_instructions: false, show_practice: false, num_practice: 5, num_trials: 0 }, + { show_instructions: false, show_practice: false, num_practice: 0, num_trials: 5 }, + ]; + + edgeCaseConfigs.forEach((config, index) => { + expect(() => { + const timeline = createTimeline(jsPsych, config); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + }); + + it("should always include completion screen even with no trials", () => { + const timeline = createTimeline(jsPsych, { + show_instructions: false, + show_practice: false, + num_practice: 0, + num_trials: 0 + }); + + // Should have at least the main intro and completion screens + expect(timeline.timeline.length).toBeGreaterThanOrEqual(2); + + // Check that completion screen is present + const completionScreen = timeline.timeline.find(trial => + trial.data && trial.data.task === 'complete' + ); + expect(completionScreen).toBeDefined(); + }); + }); + + describe("Responsive Sizing", () => { + it("should add CSS custom property for stimuli count", () => { + const html3 = createFlankerStim('left', true, fish_stimuli, 3); + const html7 = createFlankerStim('left', true, fish_stimuli, 7); + const html15 = createFlankerStim('left', true, fish_stimuli, 15); + + expect(html3).toContain('--stimuli-count: 3'); + expect(html7).toContain('--stimuli-count: 7'); + expect(html15).toContain('--stimuli-count: 15'); + }); + + it("should add CSS custom property to practice stimuli", () => { + const practiceHtml = createPracticeStim('right', false, arrow_stimuli, 9); + + expect(practiceHtml).toContain('--stimuli-count: 9'); + expect(practiceHtml).toContain('practice'); + }); + + it("should handle different stimuli counts with custom property", () => { + const testCounts = [3, 5, 7, 9, 11, 15, 21]; + + testCounts.forEach(count => { + const html = createFlankerStim('left', true, default_stimuli, count); + expect(html).toContain(`--stimuli-count: ${count}`); + expect(html).toContain('flanker-stim'); + }); + }); + + it("should work with edge cases for responsive sizing", () => { + // Test with very large count + const htmlLarge = createFlankerStim('left', true, fish_stimuli, 25); + expect(htmlLarge).toContain('--stimuli-count: 25'); + + // Test with minimum count (enforced to 3) + const htmlMin = createFlankerStim('left', true, fish_stimuli, 1); + expect(htmlMin).toContain('--stimuli-count: 3'); + + // Test with invalid input (should fall back to default) + const htmlInvalid = createFlankerStim('left', true, fish_stimuli, NaN); + expect(htmlInvalid).toContain('--stimuli-count: 5'); + }); + }); }); \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index 781ff4b5..1f944751 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -1,142 +1,271 @@ import { JsPsych } from "jspsych"; import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; -import { trial_text, instruction_pages } from './text'; +import { trial_text, instruction_pages, tts_config, tts_text } from './text'; import { layered_stimuli, fish_only, arrow_only } from './stimuli'; // ============================================================================ -// LAYERED SVG UTILITIES +// TTS (TEXT-TO-SPEECH) UTILITIES // ============================================================================ +// Global TTS state management +let currentUtterance: SpeechSynthesisUtterance | null = null; +let speechQueue: string[] = []; +let speechEnabled = false; + +// Initialize TTS functionality +export function initializeTTS(config = tts_config) { + speechEnabled = 'speechSynthesis' in window; + + if (!speechEnabled) { + console.warn('Text-to-Speech not supported in this browser'); + return false; + } + + // Stop any existing speech + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + + return true; +} + +// Get available voices +export function getAvailableVoices(): SpeechSynthesisVoice[] { + return speechSynthesis.getVoices(); +} + +// Create speech utterance with configuration +export function createUtterance(text: string, config = tts_config): SpeechSynthesisUtterance { + const utterance = new SpeechSynthesisUtterance(text); + + // Apply voice settings + utterance.rate = config.rate; + utterance.pitch = config.pitch; + utterance.volume = config.volume; + utterance.lang = config.lang; + + // Set voice if specified + if (config.voice_name) { + const voices = getAvailableVoices(); + const selectedVoice = voices.find(voice => + voice.name.includes(config.voice_name) || voice.lang === config.lang + ); + if (selectedVoice) { + utterance.voice = selectedVoice; + } + } + + return utterance; +} + +// Stop current speech +export function stopSpeech() { + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + currentUtterance = null; + speechQueue = []; +} + +// Speak text with delay and configuration +export function speakText(text: string, config = tts_config, delay = config.speech_delay): Promise { + return new Promise((resolve) => { + if (!speechEnabled || !text.trim()) { + resolve(); + return; + } + + setTimeout(() => { + // Stop any current speech + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + + currentUtterance = createUtterance(text, config); + + currentUtterance.onend = () => { + currentUtterance = null; + resolve(); + }; + + currentUtterance.onerror = () => { + currentUtterance = null; + resolve(); + }; + + speechSynthesis.speak(currentUtterance); + }, delay); + }); +} + +// Speak instruction page content +export function speakInstructionPage(pageContent: string, config = tts_config): Promise { + if (!config.speak_instructions) { + return Promise.resolve(); + } + + // Remove HTML tags and clean up text for speech + const cleanText = pageContent + .replace(/<[^>]*>/g, ' ') // Remove HTML tags + .replace(/\s+/g, ' ') // Normalize whitespace + .trim(); + + return speakText(cleanText, config); +} + +// Speak trial prompt +export function speakTrialPrompt(config = tts_config): Promise { + if (!config.speak_prompts) { + return Promise.resolve(); + } + + return speakText(tts_text.trial_prompt_spoken, config); +} + +// Speak feedback +export function speakFeedback(isCorrect: boolean, config = tts_config): Promise { + if (!config.speak_feedback) { + return Promise.resolve(); + } + + const feedbackText = isCorrect ? tts_text.correct_spoken : tts_text.incorrect_spoken; + return speakText(feedbackText, config); +} + +// Add keyboard listener for speech control +export function addSpeechControls(config = tts_config) { + if (!config.allow_skip) return undefined; + + const handleKeyPress = (event: KeyboardEvent) => { + if (event.code === 'Space' && speechSynthesis.speaking) { + event.preventDefault(); + stopSpeech(); + } + }; + + document.addEventListener('keydown', handleKeyPress); + + // Return cleanup function + return () => { + document.removeEventListener('keydown', handleKeyPress); + }; +} + // ============================================================================ // SVG UTILITIES // ============================================================================ -// Function to horizontally flip an SVG - works for all SVGs +// Robust SVG flipping with proper alignment handling function flipSVG(svgString: string): string { - // Parse the SVG to extract viewBox and content - const svgMatch = svgString.match(/]*>/); - if (!svgMatch) return svgString; - - const svgTag = svgMatch[0]; - const svgContent = svgString.replace(svgTag, '').replace('', ''); - - // Extract viewBox if it exists - const viewBoxMatch = svgTag.match(/viewBox="([^"]+)"/); - let viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 48 48'; - const viewBoxParts = viewBox.split(' '); - const vbWidth = parseFloat(viewBoxParts[2]) || 48; - - // Extract width and height - const widthMatch = svgTag.match(/width="([^"]+)"/); - const heightMatch = svgTag.match(/height="([^"]+)"/); - const width = widthMatch ? widthMatch[1] : '48'; - const height = heightMatch ? heightMatch[1] : '48'; - - // Check if SVG already has a horizontal flip transform - const hasFlipTransform = svgTag.includes('transform="matrix(-1,') || - svgTag.includes('transform="matrix(-1 ') || - svgTag.includes('transform="scale(-1'); - - // If already flipped, remove the flip transform instead of adding another - if (hasFlipTransform) { - // Remove existing flip transforms and return the content directly - let cleanedTag = svgTag - .replace(/transform="[^"]*"/g, '') - .replace(/\s+/g, ' ') - .trim(); - - // Ensure we have proper attributes - if (!cleanedTag.includes('xmlns')) { - cleanedTag = cleanedTag.replace(' { + return match.replace(/transform:\s*scaleX\(-1\);?/, '').replace(/style=""/, ''); + }); + } + + // Parse SVG to get dimensions for proper transform calculation + const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/); + const widthMatch = svgString.match(/width="([^"]+)"/); + + let translateX = 0; + if (viewBoxMatch) { + const viewBox = viewBoxMatch[1].split(/\s+/); + if (viewBox.length >= 3) { + translateX = parseFloat(viewBox[2]) || 0; // width from viewBox } - - return `${cleanedTag}${svgContent}`; + } else if (widthMatch) { + translateX = parseFloat(widthMatch[1]) || 0; + } + + // Apply proper SVG transform that maintains alignment + if (translateX > 0) { + // Insert transform into the SVG element itself for better precision + return svgString.replace( + /]*)>/, + `` + ).replace(/<\/svg>$/, ''); } else { - // Create a new SVG with a wrapper group that flips the content - return ` - - ${svgContent} - - `; + // Fallback to CSS transform with inline-block container + return `${svgString}`; } } -// Function to layer multiple SVGs on top of each other +// Simple SVG layering using CSS function layerSVGs(svgArray: string[]): string { if (!svgArray || !Array.isArray(svgArray) || svgArray.length === 0) return ''; if (svgArray.length === 1) return svgArray[0]; - // Create a container div with relative positioning - let layeredHTML = '
'; - - svgArray.forEach((svg, index) => { - const style = index === 0 - ? 'position: relative;' - : 'position: absolute; top: 0; left: 0;'; - layeredHTML += `
${svg}
`; - }); - - layeredHTML += '
'; - return layeredHTML; + // Layer SVGs using CSS positioning + return ` + ${svgArray.map((svg, index) => + `${svg}` + ).join('')} + `; } -// Function to process layered stimuli - creates left/right from SVG arrays -function processLayeredStimuli(stimuli: { left?: string[], right?: string[] } | string[]) { - // If it's a simple array, treat it as right-facing +// Simplified stimulus processing +function processStimuli(stimuli: { left?: string[], right?: string[] } | string[] | { left: string, right: string }) { + // If already processed (has left/right strings), return as-is + if (stimuli && typeof stimuli === 'object' && !Array.isArray(stimuli) && + typeof stimuli.left === 'string' && typeof stimuli.right === 'string') { + return stimuli as { left: string, right: string }; + } + + // If simple array, treat as right-facing and generate left by flipping if (Array.isArray(stimuli)) { - const rightLayered = layerSVGs(stimuli); - const leftLayered = layerSVGs(stimuli.map(svg => flipSVG(svg))); return { - left: leftLayered, - right: rightLayered + right: layerSVGs(stimuli), + left: flipSVG(layerSVGs(stimuli)) }; } - // If object with left/right arrays - if (stimuli.left && !stimuli.right) { - const leftLayered = layerSVGs(stimuli.left); - const rightLayered = layerSVGs(stimuli.left.map(svg => flipSVG(svg))); - return { - left: leftLayered, - right: rightLayered - }; - } else if (stimuli.right && !stimuli.left) { - const rightLayered = layerSVGs(stimuli.right); - const leftLayered = layerSVGs(stimuli.right.map(svg => flipSVG(svg))); - return { - left: leftLayered, - right: rightLayered - }; - } else if (stimuli.left && stimuli.right) { - return { - left: layerSVGs(stimuli.left), - right: layerSVGs(stimuli.right) - }; - } else { - // Neither provided, use default layered - const rightLayered = layerSVGs(layered_stimuli); - const leftLayered = layerSVGs(layered_stimuli.map(svg => flipSVG(svg))); - return { - left: leftLayered, - right: rightLayered - }; + // If object with arrays + if (stimuli && typeof stimuli === 'object') { + const obj = stimuli as { left?: string[], right?: string[] }; + if (obj.left && obj.right) { + return { + left: layerSVGs(obj.left), + right: layerSVGs(obj.right) + }; + } else if (obj.right) { + return { + right: layerSVGs(obj.right), + left: flipSVG(layerSVGs(obj.right)) + }; + } else if (obj.left) { + return { + left: layerSVGs(obj.left), + right: flipSVG(layerSVGs(obj.left)) + }; + } } + + // Default fallback + return { + right: layerSVGs(layered_stimuli), + left: flipSVG(layerSVGs(layered_stimuli)) + }; } -export const default_stimuli = processLayeredStimuli(layered_stimuli); - -export const fish_stimuli = processLayeredStimuli(fish_only); - -export const arrow_stimuli = processLayeredStimuli(arrow_only); +// Pre-processed stimuli for convenience (lazy-loaded) +export const default_stimuli = processStimuli(layered_stimuli); +export const fish_stimuli = processStimuli(fish_only); +export const arrow_stimuli = processStimuli(arrow_only); // ============================================================================ // STIMULUS CREATION FUNCTIONS @@ -148,18 +277,12 @@ export function createFlankerStim(direction, congruent, stimuli: string[] | { le const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); const halfFlankers = Math.floor(validAmount / 2); - // Handle different input types - let processedStimuli; - if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { - processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); - } else { - // Already processed stimuli with left/right strings - processedStimuli = stimuli as { left: string; right: string }; - } + // Process stimuli + const processedStimuli = processStimuli(stimuli); const center = processedStimuli[direction]; const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; - let html = `
`; + let html = `
`; // Add left flankers for (let i = 0; i < halfFlankers; i++) { @@ -184,18 +307,12 @@ export function createPracticeStim(direction, congruent, stimuli: string[] | { l const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); const halfFlankers = Math.floor(validAmount / 2); - // Handle different input types - let processedStimuli; - if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { - processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); - } else { - // Already processed stimuli with left/right strings - processedStimuli = stimuli as { left: string; right: string }; - } + // Process stimuli + const processedStimuli = processStimuli(stimuli); const center = processedStimuli[direction]; const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; - let html = `
`; + let html = `
`; // Add left flankers for (let i = 0; i < halfFlankers; i++) { @@ -228,6 +345,18 @@ export interface FlankerConfig { show_practice?: boolean; num_practice?: number; num_trials?: number; + + // TTS Configuration + enable_tts?: boolean; // Enable text-to-speech functionality + tts_voice?: string; // Voice name to use for TTS + tts_rate?: number; // Speech rate (0.1 to 10) + tts_pitch?: number; // Speech pitch (0 to 2) + tts_volume?: number; // Speech volume (0 to 1) + tts_lang?: string; // Language code for TTS + speak_instructions?: boolean; // Speak instruction pages + speak_prompts?: boolean; // Speak trial prompts + speak_feedback?: boolean; // Speak feedback messages + auto_speak?: boolean; // Automatically start speaking when content loads } // ============================================================================ @@ -270,8 +399,44 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { show_practice = true, num_practice = 8, num_trials = 20, + + // TTS configuration + enable_tts = false, + tts_voice = tts_config.voice_name, + tts_rate = tts_config.rate, + tts_pitch = tts_config.pitch, + tts_volume = tts_config.volume, + tts_lang = tts_config.lang, + speak_instructions = tts_config.speak_instructions, + speak_prompts = tts_config.speak_prompts, + speak_feedback = tts_config.speak_feedback, + auto_speak = tts_config.auto_speak_on_load, } = config; + // Initialize TTS if enabled + let ttsSettings = { ...tts_config }; + let speechCleanup: (() => void) | undefined; + + if (enable_tts) { + ttsSettings = { + ...tts_config, + voice_name: tts_voice, + rate: tts_rate, + pitch: tts_pitch, + volume: tts_volume, + lang: tts_lang, + speak_instructions, + speak_prompts, + speak_feedback, + auto_speak_on_load: auto_speak, + }; + + const ttsInitialized = initializeTTS(ttsSettings); + if (ttsInitialized) { + speechCleanup = addSpeechControls(ttsSettings); + } + } + // Determine which stimuli to use based on priority: // 1. svg override parameter (highest priority) // 2. custom_stimuli @@ -310,10 +475,23 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { ` : ''} ${page.start_prompt ? `

${page.start_prompt}

` : ''} + ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''}
`, choices: page.buttons || [trial_text.continue_button], - data: { task: 'instructions', page: index + 1 } + data: { task: 'instructions', page: index + 1 }, + on_load: enable_tts ? function() { + if (index === 0) { + // Speak welcome message for first page + speakText(tts_text.welcome_spoken, ttsSettings); + } else if (index === 1) { + // Speak task explanation for second page + speakText(tts_text.task_explanation_spoken, ttsSettings); + } else if (index === 2) { + // Speak directions for third page + speakText(tts_text.directions_spoken, ttsSettings); + } + } : undefined }); }); } @@ -327,34 +505,160 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) {

${trial_text.practice_header}

${trial_text.practice_intro_message}

+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''}
`, choices: [trial_text.start_button], - data: { task: 'practice-intro' } + data: { task: 'practice-intro' }, + on_load: enable_tts ? function() { + speakText(tts_text.practice_start_spoken, ttsSettings); + } : undefined }); - // Practice trials - const practice_variables = [ + // Only add practice trials if num_practice > 0 + if (num_practice > 0) { + // Practice trials + const practice_variables = [ + {direction: 'left', congruent: true}, + {direction: 'left', congruent: false}, + {direction: 'right', congruent: true}, + {direction: 'right', congruent: false}, + ]; + + const practice_trials = jsPsych.randomization.repeat(practice_variables, Math.floor(num_practice/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(practice_variables, num_practice%4)); + + // Fixation + const practice_fixation = { + type: jsPsychHtmlButtonResponse, + stimulus: `
${trial_text.fixation_cross}
`, + choices: [], + trial_duration: fixation_duration, + data: { task: 'fixation', phase: 'practice' } + }; + + // Practice trial + const practice_trial = { + type: jsPsychHtmlButtonResponse, + stimulus: function() { + const direction = jsPsych.evaluateTimelineVariable('direction'); + const congruent = jsPsych.evaluateTimelineVariable('congruent'); + return ` +
+
${trial_text.main_task_prompt}
+ ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} +
+ `; + }, + choices: [trial_text.left_button, trial_text.right_button], + data: { + task: 'flanker', + phase: 'practice', + direction: jsPsych.timelineVariable('direction'), + congruent: jsPsych.timelineVariable('congruent') + }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_prompts) { + speakTrialPrompt(ttsSettings); + } + } : undefined, + on_finish: function(data: any) { + const correct_response = data.direction === 'left' ? 0 : 1; + data.correct = data.response === correct_response; + } + }; + + // Feedback + const practice_feedback = { + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const last_trial = jsPsych.data.get().last(1).values()[0]; + const feedback = last_trial.correct ? trial_text.correct_feedback : trial_text.incorrect_feedback; + return ``; + }, + choices: [], + trial_duration: 1000, + data: { task: 'feedback', phase: 'practice' }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_feedback) { + const last_trial = jsPsych.data.get().last(1).values()[0]; + speakFeedback(last_trial.correct, ttsSettings); + } + } : undefined + }; + + // Practice timeline + timeline.push({ + timeline: [practice_fixation, practice_trial, practice_feedback], + timeline_variables: practice_trials, + randomize_order: true + }); + } + + // Practice completion + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); + const performance = calculatePerformance(practice_data); + + return ` +
+

${trial_text.practice_complete_header}

+

${trial_text.practice_complete_message}

+

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} +
+ `; + }, + choices: [trial_text.continue_button], + data: { task: 'practice-complete' }, + on_load: enable_tts ? function() { + speakText(tts_text.practice_complete_spoken, ttsSettings); + } : undefined + }); + } + + // Main task + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
+

${trial_text.main_task_header}

+

${trial_text.main_task_intro}

+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} +
+ `, + choices: [trial_text.ready_button], + data: { task: 'main-intro' }, + on_load: enable_tts ? function() { + speakText(tts_text.main_task_start_spoken, ttsSettings); + } : undefined + }); + + // Main trials - only add if num_trials > 0 + if (num_trials > 0) { + const trial_variables = [ {direction: 'left', congruent: true}, {direction: 'left', congruent: false}, {direction: 'right', congruent: true}, {direction: 'right', congruent: false}, ]; - const practice_trials = jsPsych.randomization.repeat(practice_variables, Math.floor(num_practice/4)) - .concat(jsPsych.randomization.sampleWithoutReplacement(practice_variables, num_practice%4)); + const main_trials = jsPsych.randomization.repeat(trial_variables, Math.floor(num_trials/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(trial_variables, num_trials%4)); // Fixation - const practice_fixation = { + const fixation = { type: jsPsychHtmlButtonResponse, stimulus: `
${trial_text.fixation_cross}
`, choices: [], trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'practice' } + data: { task: 'fixation', phase: 'main' } }; - // Practice trial - const practice_trial = { + // Main trial + const flanker_trial = { type: jsPsychHtmlButtonResponse, stimulus: function() { const direction = jsPsych.evaluateTimelineVariable('direction'); @@ -362,129 +666,36 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { return `
${trial_text.main_task_prompt}
- ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} + ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)}
`; }, choices: [trial_text.left_button, trial_text.right_button], data: { task: 'flanker', - phase: 'practice', + phase: 'main', direction: jsPsych.timelineVariable('direction'), congruent: jsPsych.timelineVariable('congruent') }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_prompts) { + speakTrialPrompt(ttsSettings); + } + } : undefined, on_finish: function(data: any) { const correct_response = data.direction === 'left' ? 0 : 1; data.correct = data.response === correct_response; } }; - // Feedback - const practice_feedback = { - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const last_trial = jsPsych.data.get().last(1).values()[0]; - const feedback = last_trial.correct ? trial_text.correct_feedback : trial_text.incorrect_feedback; - return ``; - }, - choices: [], - trial_duration: 1000, - data: { task: 'feedback', phase: 'practice' } - }; - - // Practice timeline + // Main task timeline timeline.push({ - timeline: [practice_fixation, practice_trial, practice_feedback], - timeline_variables: practice_trials, + timeline: [fixation, flanker_trial], + timeline_variables: main_trials, randomize_order: true }); - - // Practice completion - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); - const performance = calculatePerformance(practice_data); - - return ` -
-

${trial_text.practice_complete_header}

-

${trial_text.practice_complete_message}

-

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

-
- `; - }, - choices: [trial_text.continue_button], - data: { task: 'practice-complete' } - }); } - // Main task - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
-

${trial_text.main_task_header}

-

${trial_text.main_task_intro}

-
- `, - choices: [trial_text.ready_button], - data: { task: 'main-intro' } - }); - - // Main trials - const trial_variables = [ - {direction: 'left', congruent: true}, - {direction: 'left', congruent: false}, - {direction: 'right', congruent: true}, - {direction: 'right', congruent: false}, - ]; - - const main_trials = jsPsych.randomization.repeat(trial_variables, Math.floor(num_trials/4)) - .concat(jsPsych.randomization.sampleWithoutReplacement(trial_variables, num_trials%4)); - - // Fixation - const fixation = { - type: jsPsychHtmlButtonResponse, - stimulus: `
${trial_text.fixation_cross}
`, - choices: [], - trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'main' } - }; - - // Main trial - const flanker_trial = { - type: jsPsychHtmlButtonResponse, - stimulus: function() { - const direction = jsPsych.evaluateTimelineVariable('direction'); - const congruent = jsPsych.evaluateTimelineVariable('congruent'); - return ` -
-
${trial_text.main_task_prompt}
- ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)} -
- `; - }, - choices: [trial_text.left_button, trial_text.right_button], - data: { - task: 'flanker', - phase: 'main', - direction: jsPsych.timelineVariable('direction'), - congruent: jsPsych.timelineVariable('congruent') - }, - on_finish: function(data: any) { - const correct_response = data.direction === 'left' ? 0 : 1; - data.correct = data.response === correct_response; - } - }; - - // Main task timeline - timeline.push({ - timeline: [fixation, flanker_trial], - timeline_variables: main_trials, - randomize_order: true - }); - // Completion timeline.push({ type: jsPsychHtmlButtonResponse, @@ -501,11 +712,24 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) {

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

${trial_text.response_time_label} ${performance.mean_rt.toFixed(0)}ms

+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''}
`; }, choices: [trial_text.end_button], - data: { task: 'complete' } + data: { task: 'complete' }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_completion) { + speakText(tts_text.task_complete_spoken, ttsSettings); + } + } : undefined, + on_finish: function() { + // Cleanup speech controls when task is complete + if (speechCleanup) { + speechCleanup(); + } + stopSpeech(); + } }); return { @@ -527,11 +751,21 @@ export const timelineUnits = { export const utils = { calculatePerformance, createFlankerStim, - createPracticeStim + createPracticeStim, + + // TTS utilities + initializeTTS, + speakText, + stopSpeech, + getAvailableVoices, + speakInstructionPage, + speakTrialPrompt, + speakFeedback, + addSpeechControls }; // Export text for external use -export { trial_text, instruction_pages } from './text'; +export { trial_text, instruction_pages, tts_config, tts_text } from './text'; // Default export export default { createTimeline, timelineUnits, utils }; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index3.ts b/packages/flanker-inhibitory/src/index3.ts new file mode 100644 index 00000000..4f0ca122 --- /dev/null +++ b/packages/flanker-inhibitory/src/index3.ts @@ -0,0 +1,796 @@ +import { JsPsych } from "jspsych"; +import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; +import { trial_text, instruction_pages, tts_config, tts_text } from './text'; +import { layered_stimuli, fish_only, arrow_only } from './stimuli'; + +// ============================================================================ +// TTS (TEXT-TO-SPEECH) UTILITIES +// ============================================================================ + +// Global TTS state management +let currentUtterance: SpeechSynthesisUtterance | null = null; +let speechQueue: string[] = []; +let speechEnabled = false; + +// Initialize TTS functionality +export function initializeTTS(config = tts_config) { + speechEnabled = 'speechSynthesis' in window; + + if (!speechEnabled) { + console.warn('Text-to-Speech not supported in this browser'); + return false; + } + + // Stop any existing speech + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + + return true; +} + +// Get available voices +export function getAvailableVoices(): SpeechSynthesisVoice[] { + return speechSynthesis.getVoices(); +} + +// Create speech utterance with configuration +export function createUtterance(text: string, config = tts_config): SpeechSynthesisUtterance { + const utterance = new SpeechSynthesisUtterance(text); + + // Apply voice settings + utterance.rate = config.rate; + utterance.pitch = config.pitch; + utterance.volume = config.volume; + utterance.lang = config.lang; + + // Set voice if specified + if (config.voice_name) { + const voices = getAvailableVoices(); + const selectedVoice = voices.find(voice => + voice.name.includes(config.voice_name) || voice.lang === config.lang + ); + if (selectedVoice) { + utterance.voice = selectedVoice; + } + } + + return utterance; +} + +// Stop current speech +export function stopSpeech() { + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + currentUtterance = null; + speechQueue = []; +} + +// Speak text with delay and configuration +export function speakText(text: string, config = tts_config, delay = config.speech_delay): Promise { + return new Promise((resolve) => { + if (!speechEnabled || !text.trim()) { + resolve(); + return; + } + + setTimeout(() => { + // Stop any current speech + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + + currentUtterance = createUtterance(text, config); + + currentUtterance.onend = () => { + currentUtterance = null; + resolve(); + }; + + currentUtterance.onerror = () => { + currentUtterance = null; + resolve(); + }; + + speechSynthesis.speak(currentUtterance); + }, delay); + }); +} + +// Speak instruction page content +export function speakInstructionPage(pageContent: string, config = tts_config): Promise { + if (!config.speak_instructions) { + return Promise.resolve(); + } + + // Remove HTML tags and clean up text for speech + const cleanText = pageContent + .replace(/<[^>]*>/g, ' ') // Remove HTML tags + .replace(/\s+/g, ' ') // Normalize whitespace + .trim(); + + return speakText(cleanText, config); +} + +// Speak trial prompt +export function speakTrialPrompt(config = tts_config): Promise { + if (!config.speak_prompts) { + return Promise.resolve(); + } + + return speakText(tts_text.trial_prompt_spoken, config); +} + +// Speak feedback +export function speakFeedback(isCorrect: boolean, config = tts_config): Promise { + if (!config.speak_feedback) { + return Promise.resolve(); + } + + const feedbackText = isCorrect ? tts_text.correct_spoken : tts_text.incorrect_spoken; + return speakText(feedbackText, config); +} + +// Add keyboard listener for speech control +export function addSpeechControls(config = tts_config) { + if (!config.allow_skip) return undefined; + + const handleKeyPress = (event: KeyboardEvent) => { + if (event.code === 'Space' && speechSynthesis.speaking) { + event.preventDefault(); + stopSpeech(); + } + }; + + document.addEventListener('keydown', handleKeyPress); + + // Return cleanup function + return () => { + document.removeEventListener('keydown', handleKeyPress); + }; +} + +// ============================================================================ +// SVG UTILITIES +// ============================================================================ + +// Function to horizontally flip an SVG - works for all SVGs +function flipSVG(svgString: string): string { + // Parse the SVG to extract viewBox and content + const svgMatch = svgString.match(/]*>/); + if (!svgMatch) return svgString; + + const svgTag = svgMatch[0]; + const svgContent = svgString.replace(svgTag, '').replace('', ''); + + // Extract viewBox if it exists + const viewBoxMatch = svgTag.match(/viewBox="([^"]+)"/); + let viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 48 48'; + const viewBoxParts = viewBox.split(' '); + const vbWidth = parseFloat(viewBoxParts[2]) || 48; + + // Extract width and height + const widthMatch = svgTag.match(/width="([^"]+)"/); + const heightMatch = svgTag.match(/height="([^"]+)"/); + const width = widthMatch ? widthMatch[1] : '48'; + const height = heightMatch ? heightMatch[1] : '48'; + + // Check if SVG already has a horizontal flip transform + const hasFlipTransform = svgTag.includes('transform="matrix(-1,') || + svgTag.includes('transform="matrix(-1 ') || + svgTag.includes('transform="scale(-1'); + + // If already flipped, remove the flip transform instead of adding another + if (hasFlipTransform) { + // Remove existing flip transforms and return the content directly + let cleanedTag = svgTag + .replace(/transform="[^"]*"/g, '') + .replace(/\s+/g, ' ') + .trim(); + + // Ensure we have proper attributes + if (!cleanedTag.includes('xmlns')) { + cleanedTag = cleanedTag.replace('`; + } else { + // Create a new SVG with a wrapper group that flips the content + return ` + + ${svgContent} + + `; + } +} + +// Function to layer multiple SVGs on top of each other +function layerSVGs(svgArray: string[]): string { + if (!svgArray || !Array.isArray(svgArray) || svgArray.length === 0) return ''; + if (svgArray.length === 1) return svgArray[0]; + + // Create a container div with relative positioning + let layeredHTML = '
'; + + svgArray.forEach((svg, index) => { + const style = index === 0 + ? 'position: relative;' + : 'position: absolute; top: 0; left: 0;'; + layeredHTML += `
${svg}
`; + }); + + layeredHTML += '
'; + return layeredHTML; +} + +// Function to process layered stimuli - creates left/right from SVG arrays +function processLayeredStimuli(stimuli: { left?: string[], right?: string[] } | string[]) { + // If it's a simple array, treat it as right-facing + if (Array.isArray(stimuli)) { + const rightLayered = layerSVGs(stimuli); + const leftLayered = layerSVGs(stimuli.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } + + // If object with left/right arrays + if (stimuli.left && !stimuli.right) { + const leftLayered = layerSVGs(stimuli.left); + const rightLayered = layerSVGs(stimuli.left.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } else if (stimuli.right && !stimuli.left) { + const rightLayered = layerSVGs(stimuli.right); + const leftLayered = layerSVGs(stimuli.right.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } else if (stimuli.left && stimuli.right) { + return { + left: layerSVGs(stimuli.left), + right: layerSVGs(stimuli.right) + }; + } else { + // Neither provided, use default layered + const rightLayered = layerSVGs(layered_stimuli); + const leftLayered = layerSVGs(layered_stimuli.map(svg => flipSVG(svg))); + return { + left: leftLayered, + right: rightLayered + }; + } +} + +export const default_stimuli = processLayeredStimuli(layered_stimuli); + +export const fish_stimuli = processLayeredStimuli(fish_only); + +export const arrow_stimuli = processLayeredStimuli(arrow_only); + +// ============================================================================ +// STIMULUS CREATION FUNCTIONS +// ============================================================================ + +export function createFlankerStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { + // Ensure stimuli_amount is valid (odd number ≥3) + let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; + const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); + const halfFlankers = Math.floor(validAmount / 2); + + // Handle different input types + let processedStimuli; + if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { + processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); + } else { + // Already processed stimuli with left/right strings + processedStimuli = stimuli as { left: string; right: string }; + } + const center = processedStimuli[direction]; + const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; + + let html = `
`; + + // Add left flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + // Add center stimulus + html += `${center}`; + + // Add right flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + html += `
`; + return html; +} + +export function createPracticeStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { + // Ensure stimuli_amount is valid (odd number ≥3) + let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; + const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); + const halfFlankers = Math.floor(validAmount / 2); + + // Handle different input types + let processedStimuli; + if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { + processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); + } else { + // Already processed stimuli with left/right strings + processedStimuli = stimuli as { left: string; right: string }; + } + const center = processedStimuli[direction]; + const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; + + let html = `
`; + + // Add left flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + // Add center stimulus with highlighting + html += `${center}`; + + // Add right flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + html += `
`; + return html; +} + +// ============================================================================ +// INTERFACES +// ============================================================================ + +export interface FlankerConfig { + stimuli_type?: 'fish' | 'arrow' | 'layered'; + custom_stimuli?: { left?: string[]; right?: string[] }; + svg?: string[]; // Override parameter - array of SVGs to layer + stimuli_amount?: number; // Number of stimuli in flanker display (default: 5, must be odd ≥3) + fixation_duration?: number; + show_instructions?: boolean; + show_practice?: boolean; + num_practice?: number; + num_trials?: number; + + // TTS Configuration + enable_tts?: boolean; // Enable text-to-speech functionality + tts_voice?: string; // Voice name to use for TTS + tts_rate?: number; // Speech rate (0.1 to 10) + tts_pitch?: number; // Speech pitch (0 to 2) + tts_volume?: number; // Speech volume (0 to 1) + tts_lang?: string; // Language code for TTS + speak_instructions?: boolean; // Speak instruction pages + speak_prompts?: boolean; // Speak trial prompts + speak_feedback?: boolean; // Speak feedback messages + auto_speak?: boolean; // Automatically start speaking when content loads +} + +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +export function calculatePerformance(data: any[]) { + const trial_data = data.filter(d => d.task === 'flanker'); + + if (trial_data.length === 0) { + return { accuracy: 0, mean_rt: 0, total_trials: 0 }; + } + + const correct_trials = trial_data.filter(d => d.correct); + const accuracy = (correct_trials.length / trial_data.length) * 100; + const mean_rt = correct_trials.length > 0 + ? correct_trials.reduce((sum, d) => sum + d.rt, 0) / correct_trials.length + : 0; + + return { + accuracy, + mean_rt, + total_trials: trial_data.length, + correct_trials: correct_trials.length + }; +} + +// ============================================================================ +// MAIN TIMELINE FUNCTION +// ============================================================================ + +export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { + const { + stimuli_type = 'layered', + custom_stimuli, + svg, + stimuli_amount = 5, + fixation_duration = 500, + show_instructions = true, + show_practice = true, + num_practice = 8, + num_trials = 20, + + // TTS configuration + enable_tts = false, + tts_voice = tts_config.voice_name, + tts_rate = tts_config.rate, + tts_pitch = tts_config.pitch, + tts_volume = tts_config.volume, + tts_lang = tts_config.lang, + speak_instructions = tts_config.speak_instructions, + speak_prompts = tts_config.speak_prompts, + speak_feedback = tts_config.speak_feedback, + auto_speak = tts_config.auto_speak_on_load, + } = config; + + // Initialize TTS if enabled + let ttsSettings = { ...tts_config }; + let speechCleanup: (() => void) | undefined; + + if (enable_tts) { + ttsSettings = { + ...tts_config, + voice_name: tts_voice, + rate: tts_rate, + pitch: tts_pitch, + volume: tts_volume, + lang: tts_lang, + speak_instructions, + speak_prompts, + speak_feedback, + auto_speak_on_load: auto_speak, + }; + + const ttsInitialized = initializeTTS(ttsSettings); + if (ttsInitialized) { + speechCleanup = addSpeechControls(ttsSettings); + } + } + + // Determine which stimuli to use based on priority: + // 1. svg override parameter (highest priority) + // 2. custom_stimuli + // 3. stimuli_type selection + let stimuli; + if (svg) { + stimuli = svg; // Use the SVG override array directly + } else if (custom_stimuli) { + stimuli = custom_stimuli; + } else if (stimuli_type === 'arrow') { + stimuli = arrow_stimuli; + } else if (stimuli_type === 'fish') { + stimuli = fish_stimuli; + } else { + stimuli = default_stimuli; // layered by default + } + const timeline = []; + + // Instructions + if (show_instructions) { + instruction_pages.forEach((page, index) => { + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
+ ${page.header ? `

${page.header}

` : ''} + ${page.header2 ? `

${page.header2}

` : ''} + ${page.description ? `

${page.description}

` : ''} + ${page.task_explanation ? `

${page.task_explanation}

` : ''} + ${page.performance_note ? `

${page.performance_note}

` : ''} + ${page.strategy_title ? `

${page.strategy_title}

` : ''} + ${page.strategy_intro ? `

${page.strategy_intro}

` : ''} + ${page.strategy_points ? ` +
    + ${page.strategy_points.map(point => `
  • ${point}
  • `).join('')} +
+ ` : ''} + ${page.start_prompt ? `

${page.start_prompt}

` : ''} + ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} +
+ `, + choices: page.buttons || [trial_text.continue_button], + data: { task: 'instructions', page: index + 1 }, + on_load: enable_tts ? function() { + if (index === 0) { + // Speak welcome message for first page + speakText(tts_text.welcome_spoken, ttsSettings); + } else if (index === 1) { + // Speak task explanation for second page + speakText(tts_text.task_explanation_spoken, ttsSettings); + } else if (index === 2) { + // Speak directions for third page + speakText(tts_text.directions_spoken, ttsSettings); + } + } : undefined + }); + }); + } + + // Practice + if (show_practice) { + // Practice intro + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
+

${trial_text.practice_header}

+

${trial_text.practice_intro_message}

+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} +
+ `, + choices: [trial_text.start_button], + data: { task: 'practice-intro' }, + on_load: enable_tts ? function() { + speakText(tts_text.practice_start_spoken, ttsSettings); + } : undefined + }); + + // Practice trials + const practice_variables = [ + {direction: 'left', congruent: true}, + {direction: 'left', congruent: false}, + {direction: 'right', congruent: true}, + {direction: 'right', congruent: false}, + ]; + + const practice_trials = jsPsych.randomization.repeat(practice_variables, Math.floor(num_practice/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(practice_variables, num_practice%4)); + + // Only add practice trials if there are any + if (practice_trials.length > 0) { + // Fixation + const practice_fixation = { + type: jsPsychHtmlButtonResponse, + stimulus: `
${trial_text.fixation_cross}
`, + choices: [], + trial_duration: fixation_duration, + data: { task: 'fixation', phase: 'practice' } + }; + + // Practice trial + const practice_trial = { + type: jsPsychHtmlButtonResponse, + stimulus: function() { + const direction = jsPsych.evaluateTimelineVariable('direction'); + const congruent = jsPsych.evaluateTimelineVariable('congruent'); + return ` +
+
${trial_text.main_task_prompt}
+ ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} +
+ `; + }, + choices: [trial_text.left_button, trial_text.right_button], + data: { + task: 'flanker', + phase: 'practice', + direction: jsPsych.timelineVariable('direction'), + congruent: jsPsych.timelineVariable('congruent') + }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_prompts) { + speakTrialPrompt(ttsSettings); + } + } : undefined, + on_finish: function(data: any) { + const correct_response = data.direction === 'left' ? 0 : 1; + data.correct = data.response === correct_response; + } + }; + + // Feedback + const practice_feedback = { + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const last_trial = jsPsych.data.get().last(1).values()[0]; + const feedback = last_trial.correct ? trial_text.correct_feedback : trial_text.incorrect_feedback; + return ``; + }, + choices: [], + trial_duration: 1000, + data: { task: 'feedback', phase: 'practice' }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_feedback) { + const last_trial = jsPsych.data.get().last(1).values()[0]; + speakFeedback(last_trial.correct, ttsSettings); + } + } : undefined + }; + + // Practice timeline + timeline.push({ + timeline: [practice_fixation, practice_trial, practice_feedback], + timeline_variables: practice_trials, + randomize_order: true + }); + } + + // Practice completion + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); + const performance = calculatePerformance(practice_data); + + return ` +
+

${trial_text.practice_complete_header}

+

${trial_text.practice_complete_message}

+

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} +
+ `; + }, + choices: [trial_text.continue_button], + data: { task: 'practice-complete' }, + on_load: enable_tts ? function() { + speakText(tts_text.practice_complete_spoken, ttsSettings); + } : undefined + }); + } + + // Main task + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
+

${trial_text.main_task_header}

+

${trial_text.main_task_intro}

+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} +
+ `, + choices: [trial_text.ready_button], + data: { task: 'main-intro' }, + on_load: enable_tts ? function() { + speakText(tts_text.main_task_start_spoken, ttsSettings); + } : undefined + }); + + // Main trials + const trial_variables = [ + {direction: 'left', congruent: true}, + {direction: 'left', congruent: false}, + {direction: 'right', congruent: true}, + {direction: 'right', congruent: false}, + ]; + + const main_trials = jsPsych.randomization.repeat(trial_variables, Math.floor(num_trials/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(trial_variables, num_trials%4)); + + // Fixation + const fixation = { + type: jsPsychHtmlButtonResponse, + stimulus: `
${trial_text.fixation_cross}
`, + choices: [], + trial_duration: fixation_duration, + data: { task: 'fixation', phase: 'main' } + }; + + // Main trial + const flanker_trial = { + type: jsPsychHtmlButtonResponse, + stimulus: function() { + const direction = jsPsych.evaluateTimelineVariable('direction'); + const congruent = jsPsych.evaluateTimelineVariable('congruent'); + return ` +
+
${trial_text.main_task_prompt}
+ ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)} +
+ `; + }, + choices: [trial_text.left_button, trial_text.right_button], + data: { + task: 'flanker', + phase: 'main', + direction: jsPsych.timelineVariable('direction'), + congruent: jsPsych.timelineVariable('congruent') + }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_prompts) { + speakTrialPrompt(ttsSettings); + } + } : undefined, + on_finish: function(data: any) { + const correct_response = data.direction === 'left' ? 0 : 1; + data.correct = data.response === correct_response; + } + }; + + // Main task timeline + timeline.push({ + timeline: [fixation, flanker_trial], + timeline_variables: main_trials, + randomize_order: true + }); + + // Completion + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const main_data = jsPsych.data.get().filter({task: 'flanker', phase: 'main'}).values(); + const performance = calculatePerformance(main_data); + + return ` +
+

${trial_text.task_complete_header}

+

${trial_text.task_complete_message}

+
+

${trial_text.performance_title}

+

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

+

${trial_text.response_time_label} ${performance.mean_rt.toFixed(0)}ms

+
+ ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} +
+ `; + }, + choices: [trial_text.end_button], + data: { task: 'complete' }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_completion) { + speakText(tts_text.task_complete_spoken, ttsSettings); + } + } : undefined, + on_finish: function() { + // Cleanup speech controls when task is complete + if (speechCleanup) { + speechCleanup(); + } + stopSpeech(); + } + }); + + return { + timeline: timeline + }; +} + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const timelineUnits = { + instructions: "Instructions for the flanker inhibitory control task", + practice: "Practice trials with feedback", + main: "Main flanker task trials", + completion: "Task completion screen" +}; + +export const utils = { + calculatePerformance, + createFlankerStim, + createPracticeStim, + + // TTS utilities + initializeTTS, + speakText, + stopSpeech, + getAvailableVoices, + speakInstructionPage, + speakTrialPrompt, + speakFeedback, + addSpeechControls +}; + +// Export text for external use +export { trial_text, instruction_pages, tts_config, tts_text } from './text'; + +// Default export +export default { createTimeline, timelineUnits, utils }; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/stimuli copy.ts b/packages/flanker-inhibitory/src/stimuli copy.ts new file mode 100644 index 00000000..b9030501 --- /dev/null +++ b/packages/flanker-inhibitory/src/stimuli copy.ts @@ -0,0 +1,43 @@ +// ============================================================================ +// LAYERED SVG STIMULI +// ============================================================================ + +// Fish SVG (base layer) +const fish_svg = ` + + + + + + + + +`; + +// Arrow SVG (overlay layer) +const arrow_svg = ` + +`; + +// Default layered stimuli - fish with arrow overlay +export const layered_stimuli = [ + fish_svg, + arrow_svg +]; + +// Fish only +export const fish_only = [ + fish_svg +]; + +// Arrow only +export const arrow_only = [ + arrow_svg +]; + +// Custom example - you can add more layers +export const custom_example = [ + fish_svg, + arrow_svg, + // Add more layers here if needed +]; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css index bd875127..b1e955fa 100644 --- a/packages/flanker-inhibitory/src/styles.css +++ b/packages/flanker-inhibitory/src/styles.css @@ -101,11 +101,12 @@ } .trial-prompt { - font-size: 4.5vmin; - font-weight: 600; + font-size: 3vmin; + font-weight: 200; color: #2c3e50; text-align: center; max-width: 90vw; + line-height: normal; } /* Flanker stimulus */ @@ -113,19 +114,42 @@ display: flex; align-items: center; justify-content: center; - gap: 2vmin; margin: 3vmin 0; padding: 3vmin; + width: 100%; + max-width: 95vw; + gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); + + /* Dynamic sizing based on number of stimuli and screen space */ + --base-size: min( + calc(85vw / var(--stimuli-count, 5)), + calc(70vh / 1), + 20vmin + ); + --min-size: 4vmin; + --max-size: 25vmin; + --svg-size: max(var(--min-size), min(var(--max-size), var(--base-size))); + + /* Special handling for many stimuli (> 7) to ensure readability */ + --many-stimuli-adjustment: calc(max(0, var(--stimuli-count, 5) - 7) * 0.5vmin); + --adjusted-svg-size: max(calc(var(--svg-size) + var(--many-stimuli-adjustment)), var(--min-size)); } .flanker-stim span { - display: inline-block; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + vertical-align: middle; + line-height: 0; } .flanker-stim svg { - width: 8vmin; - height: 8vmin; + width: var(--adjusted-svg-size, var(--svg-size)); + height: var(--adjusted-svg-size, var(--svg-size)); display: block; + vertical-align: middle; + flex-shrink: 0; } /* Practice highlighting */ @@ -264,13 +288,17 @@ /* Small screens */ @media (max-width: 480px) { .flanker-stim { - gap: 1vmin; + gap: max(0.3vmin, calc(85vw / var(--stimuli-count, 5) * 0.08)); padding: 2vmin; - } - - .flanker-stim svg { - width: 6vmin; - height: 6vmin; + + /* Tighter sizing for small screens */ + --base-size: min( + calc(80vw / var(--stimuli-count, 5)), + calc(50vh / 1), + 15vmin + ); + --min-size: 3vmin; + --max-size: 12vmin; } .trial-prompt { @@ -295,9 +323,17 @@ /* Medium screens */ @media (min-width: 481px) and (max-width: 768px) { - .flanker-stim svg { - width: 9vmin; - height: 9vmin; + .flanker-stim { + gap: max(0.4vmin, calc(88vw / var(--stimuli-count, 5) * 0.09)); + + /* Medium screen sizing */ + --base-size: min( + calc(82vw / var(--stimuli-count, 5)), + calc(60vh / 1), + 18vmin + ); + --min-size: 3.5vmin; + --max-size: 18vmin; } .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { @@ -309,9 +345,17 @@ /* Large screens */ @media (min-width: 769px) { - .flanker-stim svg { - width: 10vmin; - height: 10vmin; + .flanker-stim { + gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); + + /* Large screen sizing - more generous */ + --base-size: min( + calc(85vw / var(--stimuli-count, 5)), + calc(70vh / 1), + 22vmin + ); + --min-size: 4vmin; + --max-size: 30vmin; } .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts index 53e15061..4e998cc1 100644 --- a/packages/flanker-inhibitory/src/text.ts +++ b/packages/flanker-inhibitory/src/text.ts @@ -46,6 +46,59 @@ export const trial_text = { trial_progress: "Trial {current} of {total}", }; +// TTS (Text-to-Speech) configuration +export const tts_config = { + // Voice settings + voice_name: '', // Empty string uses default voice, or specify like 'Google US English' + rate: 1.0, // Speech rate (0.1 to 10) + pitch: 1.0, // Speech pitch (0 to 2) + volume: 1.0, // Speech volume (0 to 1) + + // Language and locale + lang: 'en-US', // Language code for speech synthesis + + // What text to speak aloud (can be disabled per element) + speak_instructions: true, // Speak instruction page content + speak_prompts: true, // Speak trial prompts + speak_feedback: true, // Speak correct/incorrect feedback + speak_completion: true, // Speak task completion messages + + // Auto-play settings + auto_speak_on_load: true, // Automatically start speaking when page loads + speak_button_text: false, // Whether to also speak button labels + + // Timing + speech_delay: 500, // Delay before starting speech (ms) + allow_skip: true, // Allow users to skip/stop speech +}; + +// Spoken versions of text (can be different from displayed text for clarity) +export const tts_text = { + // Instruction spoken versions + welcome_spoken: "Welcome to the Flanker Inhibitory Control Task. This is an attention and focus test.", + task_explanation_spoken: "You will see a group of fish swimming on the screen. Your job is to focus only on the middle fish and ignore the other fish around it.", + directions_spoken: "Look at which direction the middle fish is swimming and click the corresponding button. Click the left arrow if it's swimming left, or the right arrow if it's swimming right.", + + // Trial spoken versions + trial_prompt_spoken: "Look at the middle fish. Click the direction it's swimming.", + + // Feedback spoken versions + correct_spoken: "Correct", + incorrect_spoken: "Incorrect", + + // Practice spoken versions + practice_start_spoken: "Let's practice. Look at the middle fish and click the button that shows which way it's swimming.", + practice_complete_spoken: "Practice complete. Great job! Now you're ready for the real task.", + + // Main task spoken versions + main_task_start_spoken: "Now we'll start the real task. Remember to focus on the middle fish.", + task_complete_spoken: "Task complete. Thank you for participating.", + + // Controls + skip_speech_text: "Press space to skip speech", + speech_controls_note: "Speech can be skipped by pressing the space bar at any time.", +}; + /* * This is an array of page objects that have configurable texts for the instruction pages * before the actual trials. Researchers can modify these instructions to change the diff --git a/packages/flanker-inhibitory/test-alignment.js b/packages/flanker-inhibitory/test-alignment.js new file mode 100644 index 00000000..3ec5f49a --- /dev/null +++ b/packages/flanker-inhibitory/test-alignment.js @@ -0,0 +1,78 @@ +// Quick test to verify the flipSVG function works correctly +const fs = require('fs'); + +// Read the stimuli file to get the fish SVG +const stimuliContent = fs.readFileSync('./src/stimuli.ts', 'utf8'); +const fishSvgMatch = stimuliContent.match(/const fish_svg = `([^`]+)`/s); +const fishSvg = fishSvgMatch ? fishSvgMatch[1] : ''; + +console.log('=== FISH SVG ANALYSIS ==='); +console.log('Original fish SVG has existing transform:', fishSvg.includes('transform=')); +console.log('ViewBox found:', fishSvg.match(/viewBox="([^"]+)"/)?.[1] || 'not found'); + +// Simulate the flipSVG function behavior +function flipSVG(svgString) { + if (!svgString || typeof svgString !== 'string') return svgString; + + // Check if SVG already contains a flip transform + const hasExistingTransform = svgString.includes('matrix(-1,') || + svgString.includes('matrix(-1 ') || + svgString.includes('scaleX(-1)') || + svgString.includes('scale(-1'); + + console.log('Has existing transform:', hasExistingTransform); + + // If already flipped, remove the flip transform to restore original orientation + if (hasExistingTransform) { + const result = svgString + .replace(/transform="matrix\(-1,\s*0,\s*0,\s*1,\s*0,\s*0\)"/g, '') + .replace(/transform="matrix\(-1\s+0\s+0\s+1\s+0\s+0\)"/g, '') + .replace(/transform="scaleX\(-1\)"/g, '') + .replace(/transform="scale\(-1,\s*1\)"/g, '') + .replace(/transform="scale\(-1\s+1\)"/g, ''); + + console.log('After removing existing transform, still has transform:', result.includes('transform=')); + return result; + } + + // Parse SVG to get dimensions for proper transform calculation + const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/); + const widthMatch = svgString.match(/width="([^"]+)"/); + + let translateX = 0; + if (viewBoxMatch) { + const viewBox = viewBoxMatch[1].split(/\s+/); + if (viewBox.length >= 3) { + translateX = parseFloat(viewBox[2]) || 0; // width from viewBox + } + } else if (widthMatch) { + translateX = parseFloat(widthMatch[1]) || 0; + } + + console.log('Calculated translateX:', translateX); + + // Apply proper SVG transform that maintains alignment + if (translateX > 0) { + // Insert transform into the SVG element itself for better precision + return svgString.replace( + /]*)>/, + `` + ).replace(/<\/svg>$/, ''); + } else { + // Fallback to CSS transform with inline-block container + return `${svgString}`; + } +} + +console.log('\n=== TESTING FLIP FUNCTION ==='); +const flippedOnce = flipSVG(fishSvg); +console.log('After first flip - has transform:', flippedOnce.includes('transform=')); + +const flippedTwice = flipSVG(flippedOnce); +console.log('After second flip - has transform:', flippedTwice.includes('transform=')); + +console.log('\n=== SUCCESS ==='); +console.log('The flipSVG function should now properly handle fish alignment by:'); +console.log('1. Detecting existing transforms in the fish SVG'); +console.log('2. Removing them when flipping back to original orientation'); +console.log('3. Using proper SVG transforms instead of CSS when flipping'); \ No newline at end of file From a5a29bed9819c4654506365cd9313813239afb4e Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" <72775221+farhat60@users.noreply.github.com> Date: Wed, 2 Jul 2025 15:57:14 -0400 Subject: [PATCH 21/37] Update spatial-nback.md --- packages/spatial-nback/docs/spatial-nback.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/spatial-nback/docs/spatial-nback.md b/packages/spatial-nback/docs/spatial-nback.md index 3002f194..302e081c 100644 --- a/packages/spatial-nback/docs/spatial-nback.md +++ b/packages/spatial-nback/docs/spatial-nback.md @@ -1,6 +1,6 @@ # spatial-nback -A deployable spatial n-back timeline for jsPsych. This module provides a flexible implementation of the spatial n-back task, supporting custom grid sizes, n-back levels, feedback, and more. +A deployable spatial grid n-back timeline for jsPsych. This module provides a flexible implementation of the spatial n-back task, supporting custom grid sizes, n-back levels, feedback, and more. ## Parameters @@ -95,4 +95,4 @@ Default instructions template object. --- -See [src/index.ts](src/index.ts) for implementation details. \ No newline at end of file +See [src/index.ts](src/index.ts) for implementation details. From a2dea98a435b17e437ee60627b790c4adbb670f2 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 7 Jul 2025 10:21:16 -0400 Subject: [PATCH 22/37] chore: update text --- .claude/settings.local.json | 4 +++- packages/speeded-matching/src/text.ts | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index ffb44bec..813c08d8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,9 @@ "Bash(ls:*)", "WebFetch(domain:resources.nihtoolbox.org)", "Bash(npm run build:*)", - "Bash(npm test)" + "Bash(npm test)", + "Bash(npm run:*)", + "Bash(npx jest:*)" ], "deny": [] } diff --git a/packages/speeded-matching/src/text.ts b/packages/speeded-matching/src/text.ts index c3c36d6e..d9be197a 100644 --- a/packages/speeded-matching/src/text.ts +++ b/packages/speeded-matching/src/text.ts @@ -22,7 +22,7 @@ export const trial_text = { practice_complete_message: "Practice complete! Are you ready for the actual test?", // Main task instructions - main_task_prompt: "", + main_task_prompt: "Click the picture that matches the target above.", // Fixation and inter-trial fixation_cross: "+", From bfdff2058b3e9c1b2f12738a33fec3298e22e5ba Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 7 Jul 2025 12:01:07 -0400 Subject: [PATCH 23/37] fix: custom stimuli handling and SVG flipping removed custom_stimuli parameter and added custom_example to stimuli.ts instead --- .../flanker-inhibitory/examples/index.html | 63 ++++++++++--------- packages/flanker-inhibitory/src/index.ts | 42 ++++++------- packages/flanker-inhibitory/src/stimuli.ts | 19 ++++-- 3 files changed, 67 insertions(+), 57 deletions(-) diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index d2270722..82945728 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -36,10 +36,15 @@ // Configure the task with ALL available parameters set to their defaults const config = { // ===== STIMULUS CONFIGURATION ===== - stimuli_type: 'fish', // 'fish', 'arrow', or 'layered' (fish + arrow overlay) + stimuli_type: 'custom', // 'fish', 'arrow', 'custom' or 'layered' (check stimuli.ts for details) custom_stimuli: undefined, // Custom stimuli object with left/right arrays, example at end of this file - svg: undefined, // Override parameter - array of SVGs to layer (should be right-facing) - stimuli_amount: 10, // Number of stimuli in flanker display (must be odd ≥3) + svg: [ + '', + '' + + '' + + '' + ], // Override parameter - array of SVGs to layer (should be right-facing) + stimuli_amount: 5, // Number of stimuli in flanker display (must be odd ≥3) // ===== TIMING CONFIGURATION ===== fixation_duration: 500, // Duration of fixation cross in milliseconds @@ -47,7 +52,7 @@ // ===== TASK FLOW CONFIGURATION ===== show_instructions: false, // Whether to show instruction screens show_practice: false, // Whether to include practice trials - num_practice: 2, // Number of practice trials + num_practice: 3, // Number of practice trials num_trials: 20, // Number of main task trials // ===== TEXT-TO-SPEECH CONFIGURATION ===== @@ -87,34 +92,32 @@ }); // Custom stimuli with different left/right designs - // const asymmetricStimuli = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - // custom_stimuli: { - // left: [ - // '' + - // '' + - // '' - // ], - // right: [ - // '' + - // '' + - // '' - // ] - // }, - // stimuli_amount: 7 - // }); + const asymmetricStimuli = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + custom_stimuli: { + left: [ + '' + + '' + + '' + ], + right: [ + '' + + '' + + '' + ] + }, + stimuli_amount: 7 + }); // Layered custom stimuli (multiple SVG layers) - // const layeredCustom = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - // custom_stimuli: { - // right: [ - // // Base layer - circle - // '', - // // Overlay layer - arrow - // '' - // ] - // }, - // stimuli_amount: 7 - // }); + const layeredCustom = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + custom_stimuli: { + right: [ + // Overlay layer - arrow + '' + ] + }, + stimuli_amount: 7 + }); // Run the experiment jsPsych.run([task]); diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index 1f944751..61f38ea1 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -1,7 +1,7 @@ import { JsPsych } from "jspsych"; import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; import { trial_text, instruction_pages, tts_config, tts_text } from './text'; -import { layered_stimuli, fish_only, arrow_only } from './stimuli'; +import { layered_stimuli, fish_only, arrow_only, custom_example } from './stimuli'; // ============================================================================ // TTS (TEXT-TO-SPEECH) UTILITIES @@ -107,7 +107,7 @@ export function speakInstructionPage(pageContent: string, config = tts_config): // Remove HTML tags and clean up text for speech const cleanText = pageContent .replace(/<[^>]*>/g, ' ') // Remove HTML tags - .replace(/\s+/g, ' ') // Normalize whitespace + .replace(/\s+/g, ' ') // Normalize any sequence of whitespace (including those left by the previous replace) to a single space .trim(); return speakText(cleanText, config); @@ -206,14 +206,14 @@ function flipSVG(svgString: string): string { } // Simple SVG layering using CSS -function layerSVGs(svgArray: string[]): string { +function layerSVGs(svgArray: string[], flip: boolean = false): string { if (!svgArray || !Array.isArray(svgArray) || svgArray.length === 0) return ''; - if (svgArray.length === 1) return svgArray[0]; + if (svgArray.length === 1) return flip ? flipSVG(svgArray[0]) : svgArray[0]; - // Layer SVGs using CSS positioning + // Layer SVGs using CSS positioning, flip each SVG individually if needed return ` ${svgArray.map((svg, index) => - `${svg}` + `${flip ? flipSVG(svg) : svg}` ).join('')} `; } @@ -229,8 +229,8 @@ function processStimuli(stimuli: { left?: string[], right?: string[] } | string[ // If simple array, treat as right-facing and generate left by flipping if (Array.isArray(stimuli)) { return { - right: layerSVGs(stimuli), - left: flipSVG(layerSVGs(stimuli)) + right: layerSVGs(stimuli, false), + left: layerSVGs(stimuli, true) }; } @@ -239,26 +239,26 @@ function processStimuli(stimuli: { left?: string[], right?: string[] } | string[ const obj = stimuli as { left?: string[], right?: string[] }; if (obj.left && obj.right) { return { - left: layerSVGs(obj.left), - right: layerSVGs(obj.right) + left: layerSVGs(obj.left, false), + right: layerSVGs(obj.right, false) }; } else if (obj.right) { return { - right: layerSVGs(obj.right), - left: flipSVG(layerSVGs(obj.right)) + right: layerSVGs(obj.right, false), + left: layerSVGs(obj.right, true) }; } else if (obj.left) { return { - left: layerSVGs(obj.left), - right: flipSVG(layerSVGs(obj.left)) + left: layerSVGs(obj.left, false), + right: layerSVGs(obj.left, true) }; } } // Default fallback return { - right: layerSVGs(layered_stimuli), - left: flipSVG(layerSVGs(layered_stimuli)) + right: layerSVGs(layered_stimuli, false), + left: layerSVGs(layered_stimuli, true) }; } @@ -336,8 +336,7 @@ export function createPracticeStim(direction, congruent, stimuli: string[] | { l // ============================================================================ export interface FlankerConfig { - stimuli_type?: 'fish' | 'arrow' | 'layered'; - custom_stimuli?: { left?: string[]; right?: string[] }; + stimuli_type?: 'fish' | 'arrow' | 'layered' | 'custom'; svg?: string[]; // Override parameter - array of SVGs to layer stimuli_amount?: number; // Number of stimuli in flanker display (default: 5, must be odd ≥3) fixation_duration?: number; @@ -391,7 +390,6 @@ export function calculatePerformance(data: any[]) { export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { const { stimuli_type = 'layered', - custom_stimuli, svg, stimuli_amount = 5, fixation_duration = 500, @@ -443,9 +441,9 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { // 3. stimuli_type selection let stimuli; if (svg) { - stimuli = svg; // Use the SVG override array directly - } else if (custom_stimuli) { - stimuli = custom_stimuli; + stimuli = processStimuli(svg); // Process the SVG override array to handle flipping + } else if (stimuli_type === 'custom') { + stimuli = processStimuli(custom_example); } else if (stimuli_type === 'arrow') { stimuli = arrow_stimuli; } else if (stimuli_type === 'fish') { diff --git a/packages/flanker-inhibitory/src/stimuli.ts b/packages/flanker-inhibitory/src/stimuli.ts index b9030501..a2982e24 100644 --- a/packages/flanker-inhibitory/src/stimuli.ts +++ b/packages/flanker-inhibitory/src/stimuli.ts @@ -36,8 +36,17 @@ export const arrow_only = [ ]; // Custom example - you can add more layers -export const custom_example = [ - fish_svg, - arrow_svg, - // Add more layers here if needed -]; \ No newline at end of file +export const custom_example = { + left: [ + '', + '' + + '' + + '' + ], + right: [ + '', + '' + + '' + + '' + ] +}; \ No newline at end of file From 2d25bfa4b0e3e5d3339237ca5c4cfd05c5df5160 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 7 Jul 2025 12:20:39 -0400 Subject: [PATCH 24/37] fix(flanker): custom stimuli handling --- packages/flanker-inhibitory/README.md | 3 +- .../docs/flanker-inhibitory.md | 15 ++--- .../flanker-inhibitory/examples/index.html | 57 +++++++------------ packages/flanker-inhibitory/src/index.spec.ts | 14 +---- packages/flanker-inhibitory/src/index.ts | 7 +-- packages/flanker-inhibitory/src/stimuli.ts | 4 +- 6 files changed, 38 insertions(+), 62 deletions(-) diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md index d6d30d1d..e28b96f6 100644 --- a/packages/flanker-inhibitory/README.md +++ b/packages/flanker-inhibitory/README.md @@ -38,8 +38,7 @@ The following parameters can be specified in the **options** parameter. | Parameter | Type | Default | Description | |-----------|------|---------|-------------| -| stimuli_type | string | 'layered' | Type of stimuli to use: 'fish', 'arrow', or 'layered' | -| custom_stimuli | object | undefined | Custom stimuli object with left/right arrays | +| stimuli_type | string | 'layered' | Type of stimuli to use: 'fish', 'arrow', 'layered', or 'custom' | | svg | array | undefined | Override parameter - array of SVGs to layer (should be right-facing; left version auto-generated) | | stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd ≥3) | | fixation_duration | number | 500 | Duration of fixation cross in milliseconds | diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md index 7909cd34..0d9a97e0 100644 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -18,8 +18,7 @@ const timeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { Parameter | Type | Default Value | Description ----------|------|---------------|------------ -stimuli_type | string | 'layered' | Type of stimuli: 'fish', 'arrow', or 'layered' (fish + arrow) -custom_stimuli | object | undefined | Custom stimuli object with left/right arrays of SVG strings +stimuli_type | string | 'layered' | Type of stimuli: 'fish', 'arrow', 'layered' (fish + arrow), or 'custom' svg | array | undefined | Override parameter - array of right-facing SVG strings to layer (left version auto-generated) stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd number ≥3) fixation_duration | number | 500 | Duration of fixation cross display in milliseconds @@ -54,11 +53,13 @@ const customConfig = { // Use layered stimuli with custom amounts const layeredConfig = { stimuli_type: 'layered', - stimuli_amount: 9, - custom_stimuli: { - left: ['...left base...', '...left overlay...'], - right: ['...right base...', '...right overlay...'] - } + stimuli_amount: 9 +}; + +// Use custom stimuli type with predefined custom_stimulus +const customTypeConfig = { + stimuli_type: 'custom', + stimuli_amount: 7 }; // Enable TTS with custom voice settings diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index 82945728..31547dd9 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -36,8 +36,7 @@ // Configure the task with ALL available parameters set to their defaults const config = { // ===== STIMULUS CONFIGURATION ===== - stimuli_type: 'custom', // 'fish', 'arrow', 'custom' or 'layered' (check stimuli.ts for details) - custom_stimuli: undefined, // Custom stimuli object with left/right arrays, example at end of this file + stimuli_type: 'custom', // 'fish', 'arrow', 'custom', or 'layered' (check stimuli.ts for details) svg: [ '', '' + @@ -71,51 +70,37 @@ // Create the timeline const task = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, config); - // Custom stimuli with car example (left auto-generated from right) + // Custom stimuli with car example using svg override const task1 = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - - custom_stimuli: { - right: [ - // Custom car pointing right - '' + - '' + - '' + - '' + - '' + - '' - ] + svg: [ + // Custom car pointing right + '' + + '' + + '' + + '' + + '' + + '' // Left version will be auto-generated by flipping - }, + ], stimuli_amount: 5, num_practice: 0, num_trials: 16 }); - // Custom stimuli with different left/right designs - const asymmetricStimuli = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - custom_stimuli: { - left: [ - '' + - '' + - '' - ], - right: [ - '' + - '' + - '' - ] - }, + // Using custom stimuli type (uses custom_stimulus from stimuli.ts) + const customTypeExample = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + stimuli_type: 'custom', stimuli_amount: 7 }); - // Layered custom stimuli (multiple SVG layers) + // Layered SVG override (multiple SVG layers) const layeredCustom = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - custom_stimuli: { - right: [ - // Overlay layer - arrow - '' - ] - }, + svg: [ + // Base layer - circle + '', + // Overlay layer - arrow + '' + ], stimuli_amount: 7 }); diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts index 586faebe..ce0906a1 100644 --- a/packages/flanker-inhibitory/src/index.spec.ts +++ b/packages/flanker-inhibitory/src/index.spec.ts @@ -75,10 +75,12 @@ describe("Flanker Inhibitory Control Task", () => { const fishTimeline = createTimeline(jsPsych, { stimuli_type: 'fish' }); const arrowTimeline = createTimeline(jsPsych, { stimuli_type: 'arrow' }); const layeredTimeline = createTimeline(jsPsych, { stimuli_type: 'layered' }); + const customTimeline = createTimeline(jsPsych, { stimuli_type: 'custom' }); expect(fishTimeline.timeline.length).toBeGreaterThan(0); expect(arrowTimeline.timeline.length).toBeGreaterThan(0); expect(layeredTimeline.timeline.length).toBeGreaterThan(0); + expect(customTimeline.timeline.length).toBeGreaterThan(0); }); it("should handle SVG override parameter", () => { @@ -91,15 +93,6 @@ describe("Flanker Inhibitory Control Task", () => { expect(timeline.timeline.length).toBeGreaterThan(0); }); - it("should handle custom stimuli", () => { - const customStimuli = { - left: ['test left'], - right: ['test right'] - }; - - const timeline = createTimeline(jsPsych, { custom_stimuli: customStimuli }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }); it("should set custom fixation duration", () => { const customDuration = 1000; @@ -331,8 +324,7 @@ describe("Flanker Inhibitory Control Task", () => { { show_practice: true }, { num_practice: 10 }, { num_trials: 25 }, - { svg: ['test'] }, - { custom_stimuli: { left: ['left'] } } + { svg: ['test'] } ]; configs.forEach(config => { diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index 61f38ea1..91185ff0 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -1,7 +1,7 @@ import { JsPsych } from "jspsych"; import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; import { trial_text, instruction_pages, tts_config, tts_text } from './text'; -import { layered_stimuli, fish_only, arrow_only, custom_example } from './stimuli'; +import { layered_stimuli, fish_only, arrow_only, custom_stimulus } from './stimuli'; // ============================================================================ // TTS (TEXT-TO-SPEECH) UTILITIES @@ -437,13 +437,12 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { // Determine which stimuli to use based on priority: // 1. svg override parameter (highest priority) - // 2. custom_stimuli - // 3. stimuli_type selection + // 2. stimuli_type selection let stimuli; if (svg) { stimuli = processStimuli(svg); // Process the SVG override array to handle flipping } else if (stimuli_type === 'custom') { - stimuli = processStimuli(custom_example); + stimuli = processStimuli(custom_stimulus); } else if (stimuli_type === 'arrow') { stimuli = arrow_stimuli; } else if (stimuli_type === 'fish') { diff --git a/packages/flanker-inhibitory/src/stimuli.ts b/packages/flanker-inhibitory/src/stimuli.ts index a2982e24..fed70288 100644 --- a/packages/flanker-inhibitory/src/stimuli.ts +++ b/packages/flanker-inhibitory/src/stimuli.ts @@ -35,8 +35,8 @@ export const arrow_only = [ arrow_svg ]; -// Custom example - you can add more layers -export const custom_example = { +// Custom stimulus - you can add more layers +export const custom_stimulus = { left: [ '', '' + From 6575b6bee636a231585a9d7482a8a87defe18e48 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 8 Jul 2025 11:18:10 -0400 Subject: [PATCH 25/37] general html and css updates --- .../flanker-inhibitory/examples/index.html | 20 +- packages/flanker-inhibitory/src/index.spec.ts | 43 +- packages/flanker-inhibitory/src/index3.ts | 796 ------------------ .../flanker-inhibitory/src/stimuli copy.ts | 43 - packages/flanker-inhibitory/src/styles.css | 2 - packages/flanker-inhibitory/test-alignment.js | 78 -- packages/pattern-comparison-test/README.md | 50 -- .../docs/pattern-comparison-test.md | 51 -- .../examples/index.html | 578 ------------- .../pattern-comparison-test/jest.config.js | 1 - packages/pattern-comparison-test/package.json | 35 - .../pattern-comparison-test/src/index copy.ts | 9 - packages/pattern-comparison-test/src/index.ts | 632 -------------- .../pattern-comparison-test/tsconfig.json | 10 - packages/spatial-nback/package.json | 2 +- packages/speeded-matching/examples/index.html | 4 +- 16 files changed, 47 insertions(+), 2307 deletions(-) delete mode 100644 packages/flanker-inhibitory/src/index3.ts delete mode 100644 packages/flanker-inhibitory/src/stimuli copy.ts delete mode 100644 packages/flanker-inhibitory/test-alignment.js delete mode 100644 packages/pattern-comparison-test/README.md delete mode 100644 packages/pattern-comparison-test/docs/pattern-comparison-test.md delete mode 100644 packages/pattern-comparison-test/examples/index.html delete mode 100644 packages/pattern-comparison-test/jest.config.js delete mode 100644 packages/pattern-comparison-test/package.json delete mode 100644 packages/pattern-comparison-test/src/index copy.ts delete mode 100644 packages/pattern-comparison-test/src/index.ts delete mode 100644 packages/pattern-comparison-test/tsconfig.json diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index 31547dd9..980fbd15 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -23,26 +23,26 @@ const jsPsych = initJsPsych({ on_finish: function() { // Display results when task is complete - const data = jsPsych.data.get().filter({task: 'flanker-trial', trial_type: 'test'}); - const performance = jsPsychTimelineFlankerInhibitory.utils.calculatePerformance(data.values()); + // const data = jsPsych.data.get().filter({task: 'flanker-trial', trial_type: 'test'}); + // const performance = jsPsychTimelineFlankerInhibitory.utils.calculatePerformance(data.values()); console.log('Task Performance:', performance); // Optional: display CSV data - jsPsych.data.displayData(); + // jsPsych.data.displayData(); } }); // Configure the task with ALL available parameters set to their defaults const config = { // ===== STIMULUS CONFIGURATION ===== - stimuli_type: 'custom', // 'fish', 'arrow', 'custom', or 'layered' (check stimuli.ts for details) - svg: [ - '', - '' + - '' + - '' - ], // Override parameter - array of SVGs to layer (should be right-facing) + stimuli_type: 'layered', // 'fish', 'arrow', 'custom', or 'layered' (check stimuli.ts for details) + // svg: [ + // '', + // '' + + // '' + + // '' + // ], // Override parameter - array of SVGs to layer (should be right-facing) stimuli_amount: 5, // Number of stimuli in flanker display (must be odd ≥3) // ===== TIMING CONFIGURATION ===== diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts index ce0906a1..1e158a38 100644 --- a/packages/flanker-inhibitory/src/index.spec.ts +++ b/packages/flanker-inhibitory/src/index.spec.ts @@ -22,6 +22,38 @@ describe("Flanker Inhibitory Control Task", () => { jsPsych = initJsPsych(); }); + // Helper function to count only top-level spans (flanker positions) + const countFlankerPositions = (html: string): number => { + // Extract the content inside the flanker-stim div + const flankerMatch = html.match(/
]*>([\s\S]*?)<\/div>/); + if (!flankerMatch) return 0; + + const content = flankerMatch[1]; + + // Count only direct child spans - these are the stimulus positions + // Look for spans that are not nested inside other spans + let count = 0; + let depth = 0; + let i = 0; + + while (i < content.length) { + if (content.substring(i).startsWith('', i) + 1; + } else if (content.substring(i).startsWith('')) { + depth--; + i = content.indexOf('>', i) + 1; + } else { + i++; + } + } + + return count; + }; + describe("createTimeline", () => { it("should return a timeline object with timeline array", () => { const timeline = createTimeline(jsPsych); @@ -437,13 +469,6 @@ describe("Flanker Inhibitory Control Task", () => { }); describe("Stimuli Amount Functionality", () => { - // Helper function to count only top-level spans (flanker positions) - const countFlankerPositions = (html: string): number => { - // Count spans that are direct children of the flanker-stim div - // These represent the actual flanker positions, not inner SVG content - const matches = html.match(/]*>/g) || []; - return matches.length; - }; it("should default to 5 stimuli (2 left + 1 center + 2 right)", () => { const html = createFlankerStim('left', true); @@ -555,8 +580,8 @@ describe("Flanker Inhibitory Control Task", () => { const htmlNaN = createFlankerStim('left', true, layered_stimuli, NaN); const htmlInfinity = createFlankerStim('left', true, layered_stimuli, Infinity); - expect((htmlNaN.match(/]*>/g) || []).length).toBe(5); - expect((htmlInfinity.match(/]*>/g) || []).length).toBe(5); + expect(countFlankerPositions(htmlNaN)).toBe(5); + expect(countFlankerPositions(htmlInfinity)).toBe(5); }); }); diff --git a/packages/flanker-inhibitory/src/index3.ts b/packages/flanker-inhibitory/src/index3.ts deleted file mode 100644 index 4f0ca122..00000000 --- a/packages/flanker-inhibitory/src/index3.ts +++ /dev/null @@ -1,796 +0,0 @@ -import { JsPsych } from "jspsych"; -import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; -import { trial_text, instruction_pages, tts_config, tts_text } from './text'; -import { layered_stimuli, fish_only, arrow_only } from './stimuli'; - -// ============================================================================ -// TTS (TEXT-TO-SPEECH) UTILITIES -// ============================================================================ - -// Global TTS state management -let currentUtterance: SpeechSynthesisUtterance | null = null; -let speechQueue: string[] = []; -let speechEnabled = false; - -// Initialize TTS functionality -export function initializeTTS(config = tts_config) { - speechEnabled = 'speechSynthesis' in window; - - if (!speechEnabled) { - console.warn('Text-to-Speech not supported in this browser'); - return false; - } - - // Stop any existing speech - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } - - return true; -} - -// Get available voices -export function getAvailableVoices(): SpeechSynthesisVoice[] { - return speechSynthesis.getVoices(); -} - -// Create speech utterance with configuration -export function createUtterance(text: string, config = tts_config): SpeechSynthesisUtterance { - const utterance = new SpeechSynthesisUtterance(text); - - // Apply voice settings - utterance.rate = config.rate; - utterance.pitch = config.pitch; - utterance.volume = config.volume; - utterance.lang = config.lang; - - // Set voice if specified - if (config.voice_name) { - const voices = getAvailableVoices(); - const selectedVoice = voices.find(voice => - voice.name.includes(config.voice_name) || voice.lang === config.lang - ); - if (selectedVoice) { - utterance.voice = selectedVoice; - } - } - - return utterance; -} - -// Stop current speech -export function stopSpeech() { - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } - currentUtterance = null; - speechQueue = []; -} - -// Speak text with delay and configuration -export function speakText(text: string, config = tts_config, delay = config.speech_delay): Promise { - return new Promise((resolve) => { - if (!speechEnabled || !text.trim()) { - resolve(); - return; - } - - setTimeout(() => { - // Stop any current speech - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } - - currentUtterance = createUtterance(text, config); - - currentUtterance.onend = () => { - currentUtterance = null; - resolve(); - }; - - currentUtterance.onerror = () => { - currentUtterance = null; - resolve(); - }; - - speechSynthesis.speak(currentUtterance); - }, delay); - }); -} - -// Speak instruction page content -export function speakInstructionPage(pageContent: string, config = tts_config): Promise { - if (!config.speak_instructions) { - return Promise.resolve(); - } - - // Remove HTML tags and clean up text for speech - const cleanText = pageContent - .replace(/<[^>]*>/g, ' ') // Remove HTML tags - .replace(/\s+/g, ' ') // Normalize whitespace - .trim(); - - return speakText(cleanText, config); -} - -// Speak trial prompt -export function speakTrialPrompt(config = tts_config): Promise { - if (!config.speak_prompts) { - return Promise.resolve(); - } - - return speakText(tts_text.trial_prompt_spoken, config); -} - -// Speak feedback -export function speakFeedback(isCorrect: boolean, config = tts_config): Promise { - if (!config.speak_feedback) { - return Promise.resolve(); - } - - const feedbackText = isCorrect ? tts_text.correct_spoken : tts_text.incorrect_spoken; - return speakText(feedbackText, config); -} - -// Add keyboard listener for speech control -export function addSpeechControls(config = tts_config) { - if (!config.allow_skip) return undefined; - - const handleKeyPress = (event: KeyboardEvent) => { - if (event.code === 'Space' && speechSynthesis.speaking) { - event.preventDefault(); - stopSpeech(); - } - }; - - document.addEventListener('keydown', handleKeyPress); - - // Return cleanup function - return () => { - document.removeEventListener('keydown', handleKeyPress); - }; -} - -// ============================================================================ -// SVG UTILITIES -// ============================================================================ - -// Function to horizontally flip an SVG - works for all SVGs -function flipSVG(svgString: string): string { - // Parse the SVG to extract viewBox and content - const svgMatch = svgString.match(/]*>/); - if (!svgMatch) return svgString; - - const svgTag = svgMatch[0]; - const svgContent = svgString.replace(svgTag, '').replace('', ''); - - // Extract viewBox if it exists - const viewBoxMatch = svgTag.match(/viewBox="([^"]+)"/); - let viewBox = viewBoxMatch ? viewBoxMatch[1] : '0 0 48 48'; - const viewBoxParts = viewBox.split(' '); - const vbWidth = parseFloat(viewBoxParts[2]) || 48; - - // Extract width and height - const widthMatch = svgTag.match(/width="([^"]+)"/); - const heightMatch = svgTag.match(/height="([^"]+)"/); - const width = widthMatch ? widthMatch[1] : '48'; - const height = heightMatch ? heightMatch[1] : '48'; - - // Check if SVG already has a horizontal flip transform - const hasFlipTransform = svgTag.includes('transform="matrix(-1,') || - svgTag.includes('transform="matrix(-1 ') || - svgTag.includes('transform="scale(-1'); - - // If already flipped, remove the flip transform instead of adding another - if (hasFlipTransform) { - // Remove existing flip transforms and return the content directly - let cleanedTag = svgTag - .replace(/transform="[^"]*"/g, '') - .replace(/\s+/g, ' ') - .trim(); - - // Ensure we have proper attributes - if (!cleanedTag.includes('xmlns')) { - cleanedTag = cleanedTag.replace('`; - } else { - // Create a new SVG with a wrapper group that flips the content - return ` - - ${svgContent} - - `; - } -} - -// Function to layer multiple SVGs on top of each other -function layerSVGs(svgArray: string[]): string { - if (!svgArray || !Array.isArray(svgArray) || svgArray.length === 0) return ''; - if (svgArray.length === 1) return svgArray[0]; - - // Create a container div with relative positioning - let layeredHTML = '
'; - - svgArray.forEach((svg, index) => { - const style = index === 0 - ? 'position: relative;' - : 'position: absolute; top: 0; left: 0;'; - layeredHTML += `
${svg}
`; - }); - - layeredHTML += '
'; - return layeredHTML; -} - -// Function to process layered stimuli - creates left/right from SVG arrays -function processLayeredStimuli(stimuli: { left?: string[], right?: string[] } | string[]) { - // If it's a simple array, treat it as right-facing - if (Array.isArray(stimuli)) { - const rightLayered = layerSVGs(stimuli); - const leftLayered = layerSVGs(stimuli.map(svg => flipSVG(svg))); - return { - left: leftLayered, - right: rightLayered - }; - } - - // If object with left/right arrays - if (stimuli.left && !stimuli.right) { - const leftLayered = layerSVGs(stimuli.left); - const rightLayered = layerSVGs(stimuli.left.map(svg => flipSVG(svg))); - return { - left: leftLayered, - right: rightLayered - }; - } else if (stimuli.right && !stimuli.left) { - const rightLayered = layerSVGs(stimuli.right); - const leftLayered = layerSVGs(stimuli.right.map(svg => flipSVG(svg))); - return { - left: leftLayered, - right: rightLayered - }; - } else if (stimuli.left && stimuli.right) { - return { - left: layerSVGs(stimuli.left), - right: layerSVGs(stimuli.right) - }; - } else { - // Neither provided, use default layered - const rightLayered = layerSVGs(layered_stimuli); - const leftLayered = layerSVGs(layered_stimuli.map(svg => flipSVG(svg))); - return { - left: leftLayered, - right: rightLayered - }; - } -} - -export const default_stimuli = processLayeredStimuli(layered_stimuli); - -export const fish_stimuli = processLayeredStimuli(fish_only); - -export const arrow_stimuli = processLayeredStimuli(arrow_only); - -// ============================================================================ -// STIMULUS CREATION FUNCTIONS -// ============================================================================ - -export function createFlankerStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { - // Ensure stimuli_amount is valid (odd number ≥3) - let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; - const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); - const halfFlankers = Math.floor(validAmount / 2); - - // Handle different input types - let processedStimuli; - if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { - processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); - } else { - // Already processed stimuli with left/right strings - processedStimuli = stimuli as { left: string; right: string }; - } - const center = processedStimuli[direction]; - const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; - - let html = `
`; - - // Add left flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - // Add center stimulus - html += `${center}`; - - // Add right flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - html += `
`; - return html; -} - -export function createPracticeStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { - // Ensure stimuli_amount is valid (odd number ≥3) - let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; - const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); - const halfFlankers = Math.floor(validAmount / 2); - - // Handle different input types - let processedStimuli; - if (Array.isArray(stimuli) || (stimuli && ('left' in stimuli || 'right' in stimuli) && Array.isArray(stimuli.left || stimuli.right))) { - processedStimuli = processLayeredStimuli(stimuli as string[] | { left?: string[]; right?: string[] }); - } else { - // Already processed stimuli with left/right strings - processedStimuli = stimuli as { left: string; right: string }; - } - const center = processedStimuli[direction]; - const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; - - let html = `
`; - - // Add left flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - // Add center stimulus with highlighting - html += `${center}`; - - // Add right flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - html += `
`; - return html; -} - -// ============================================================================ -// INTERFACES -// ============================================================================ - -export interface FlankerConfig { - stimuli_type?: 'fish' | 'arrow' | 'layered'; - custom_stimuli?: { left?: string[]; right?: string[] }; - svg?: string[]; // Override parameter - array of SVGs to layer - stimuli_amount?: number; // Number of stimuli in flanker display (default: 5, must be odd ≥3) - fixation_duration?: number; - show_instructions?: boolean; - show_practice?: boolean; - num_practice?: number; - num_trials?: number; - - // TTS Configuration - enable_tts?: boolean; // Enable text-to-speech functionality - tts_voice?: string; // Voice name to use for TTS - tts_rate?: number; // Speech rate (0.1 to 10) - tts_pitch?: number; // Speech pitch (0 to 2) - tts_volume?: number; // Speech volume (0 to 1) - tts_lang?: string; // Language code for TTS - speak_instructions?: boolean; // Speak instruction pages - speak_prompts?: boolean; // Speak trial prompts - speak_feedback?: boolean; // Speak feedback messages - auto_speak?: boolean; // Automatically start speaking when content loads -} - -// ============================================================================ -// UTILITY FUNCTIONS -// ============================================================================ - -export function calculatePerformance(data: any[]) { - const trial_data = data.filter(d => d.task === 'flanker'); - - if (trial_data.length === 0) { - return { accuracy: 0, mean_rt: 0, total_trials: 0 }; - } - - const correct_trials = trial_data.filter(d => d.correct); - const accuracy = (correct_trials.length / trial_data.length) * 100; - const mean_rt = correct_trials.length > 0 - ? correct_trials.reduce((sum, d) => sum + d.rt, 0) / correct_trials.length - : 0; - - return { - accuracy, - mean_rt, - total_trials: trial_data.length, - correct_trials: correct_trials.length - }; -} - -// ============================================================================ -// MAIN TIMELINE FUNCTION -// ============================================================================ - -export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { - const { - stimuli_type = 'layered', - custom_stimuli, - svg, - stimuli_amount = 5, - fixation_duration = 500, - show_instructions = true, - show_practice = true, - num_practice = 8, - num_trials = 20, - - // TTS configuration - enable_tts = false, - tts_voice = tts_config.voice_name, - tts_rate = tts_config.rate, - tts_pitch = tts_config.pitch, - tts_volume = tts_config.volume, - tts_lang = tts_config.lang, - speak_instructions = tts_config.speak_instructions, - speak_prompts = tts_config.speak_prompts, - speak_feedback = tts_config.speak_feedback, - auto_speak = tts_config.auto_speak_on_load, - } = config; - - // Initialize TTS if enabled - let ttsSettings = { ...tts_config }; - let speechCleanup: (() => void) | undefined; - - if (enable_tts) { - ttsSettings = { - ...tts_config, - voice_name: tts_voice, - rate: tts_rate, - pitch: tts_pitch, - volume: tts_volume, - lang: tts_lang, - speak_instructions, - speak_prompts, - speak_feedback, - auto_speak_on_load: auto_speak, - }; - - const ttsInitialized = initializeTTS(ttsSettings); - if (ttsInitialized) { - speechCleanup = addSpeechControls(ttsSettings); - } - } - - // Determine which stimuli to use based on priority: - // 1. svg override parameter (highest priority) - // 2. custom_stimuli - // 3. stimuli_type selection - let stimuli; - if (svg) { - stimuli = svg; // Use the SVG override array directly - } else if (custom_stimuli) { - stimuli = custom_stimuli; - } else if (stimuli_type === 'arrow') { - stimuli = arrow_stimuli; - } else if (stimuli_type === 'fish') { - stimuli = fish_stimuli; - } else { - stimuli = default_stimuli; // layered by default - } - const timeline = []; - - // Instructions - if (show_instructions) { - instruction_pages.forEach((page, index) => { - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
- ${page.header ? `

${page.header}

` : ''} - ${page.header2 ? `

${page.header2}

` : ''} - ${page.description ? `

${page.description}

` : ''} - ${page.task_explanation ? `

${page.task_explanation}

` : ''} - ${page.performance_note ? `

${page.performance_note}

` : ''} - ${page.strategy_title ? `

${page.strategy_title}

` : ''} - ${page.strategy_intro ? `

${page.strategy_intro}

` : ''} - ${page.strategy_points ? ` -
    - ${page.strategy_points.map(point => `
  • ${point}
  • `).join('')} -
- ` : ''} - ${page.start_prompt ? `

${page.start_prompt}

` : ''} - ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `, - choices: page.buttons || [trial_text.continue_button], - data: { task: 'instructions', page: index + 1 }, - on_load: enable_tts ? function() { - if (index === 0) { - // Speak welcome message for first page - speakText(tts_text.welcome_spoken, ttsSettings); - } else if (index === 1) { - // Speak task explanation for second page - speakText(tts_text.task_explanation_spoken, ttsSettings); - } else if (index === 2) { - // Speak directions for third page - speakText(tts_text.directions_spoken, ttsSettings); - } - } : undefined - }); - }); - } - - // Practice - if (show_practice) { - // Practice intro - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
-

${trial_text.practice_header}

-

${trial_text.practice_intro_message}

- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `, - choices: [trial_text.start_button], - data: { task: 'practice-intro' }, - on_load: enable_tts ? function() { - speakText(tts_text.practice_start_spoken, ttsSettings); - } : undefined - }); - - // Practice trials - const practice_variables = [ - {direction: 'left', congruent: true}, - {direction: 'left', congruent: false}, - {direction: 'right', congruent: true}, - {direction: 'right', congruent: false}, - ]; - - const practice_trials = jsPsych.randomization.repeat(practice_variables, Math.floor(num_practice/4)) - .concat(jsPsych.randomization.sampleWithoutReplacement(practice_variables, num_practice%4)); - - // Only add practice trials if there are any - if (practice_trials.length > 0) { - // Fixation - const practice_fixation = { - type: jsPsychHtmlButtonResponse, - stimulus: `
${trial_text.fixation_cross}
`, - choices: [], - trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'practice' } - }; - - // Practice trial - const practice_trial = { - type: jsPsychHtmlButtonResponse, - stimulus: function() { - const direction = jsPsych.evaluateTimelineVariable('direction'); - const congruent = jsPsych.evaluateTimelineVariable('congruent'); - return ` -
-
${trial_text.main_task_prompt}
- ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} -
- `; - }, - choices: [trial_text.left_button, trial_text.right_button], - data: { - task: 'flanker', - phase: 'practice', - direction: jsPsych.timelineVariable('direction'), - congruent: jsPsych.timelineVariable('congruent') - }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_prompts) { - speakTrialPrompt(ttsSettings); - } - } : undefined, - on_finish: function(data: any) { - const correct_response = data.direction === 'left' ? 0 : 1; - data.correct = data.response === correct_response; - } - }; - - // Feedback - const practice_feedback = { - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const last_trial = jsPsych.data.get().last(1).values()[0]; - const feedback = last_trial.correct ? trial_text.correct_feedback : trial_text.incorrect_feedback; - return ``; - }, - choices: [], - trial_duration: 1000, - data: { task: 'feedback', phase: 'practice' }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_feedback) { - const last_trial = jsPsych.data.get().last(1).values()[0]; - speakFeedback(last_trial.correct, ttsSettings); - } - } : undefined - }; - - // Practice timeline - timeline.push({ - timeline: [practice_fixation, practice_trial, practice_feedback], - timeline_variables: practice_trials, - randomize_order: true - }); - } - - // Practice completion - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); - const performance = calculatePerformance(practice_data); - - return ` -
-

${trial_text.practice_complete_header}

-

${trial_text.practice_complete_message}

-

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `; - }, - choices: [trial_text.continue_button], - data: { task: 'practice-complete' }, - on_load: enable_tts ? function() { - speakText(tts_text.practice_complete_spoken, ttsSettings); - } : undefined - }); - } - - // Main task - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
-

${trial_text.main_task_header}

-

${trial_text.main_task_intro}

- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `, - choices: [trial_text.ready_button], - data: { task: 'main-intro' }, - on_load: enable_tts ? function() { - speakText(tts_text.main_task_start_spoken, ttsSettings); - } : undefined - }); - - // Main trials - const trial_variables = [ - {direction: 'left', congruent: true}, - {direction: 'left', congruent: false}, - {direction: 'right', congruent: true}, - {direction: 'right', congruent: false}, - ]; - - const main_trials = jsPsych.randomization.repeat(trial_variables, Math.floor(num_trials/4)) - .concat(jsPsych.randomization.sampleWithoutReplacement(trial_variables, num_trials%4)); - - // Fixation - const fixation = { - type: jsPsychHtmlButtonResponse, - stimulus: `
${trial_text.fixation_cross}
`, - choices: [], - trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'main' } - }; - - // Main trial - const flanker_trial = { - type: jsPsychHtmlButtonResponse, - stimulus: function() { - const direction = jsPsych.evaluateTimelineVariable('direction'); - const congruent = jsPsych.evaluateTimelineVariable('congruent'); - return ` -
-
${trial_text.main_task_prompt}
- ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)} -
- `; - }, - choices: [trial_text.left_button, trial_text.right_button], - data: { - task: 'flanker', - phase: 'main', - direction: jsPsych.timelineVariable('direction'), - congruent: jsPsych.timelineVariable('congruent') - }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_prompts) { - speakTrialPrompt(ttsSettings); - } - } : undefined, - on_finish: function(data: any) { - const correct_response = data.direction === 'left' ? 0 : 1; - data.correct = data.response === correct_response; - } - }; - - // Main task timeline - timeline.push({ - timeline: [fixation, flanker_trial], - timeline_variables: main_trials, - randomize_order: true - }); - - // Completion - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const main_data = jsPsych.data.get().filter({task: 'flanker', phase: 'main'}).values(); - const performance = calculatePerformance(main_data); - - return ` -
-

${trial_text.task_complete_header}

-

${trial_text.task_complete_message}

-
-

${trial_text.performance_title}

-

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

-

${trial_text.response_time_label} ${performance.mean_rt.toFixed(0)}ms

-
- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `; - }, - choices: [trial_text.end_button], - data: { task: 'complete' }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_completion) { - speakText(tts_text.task_complete_spoken, ttsSettings); - } - } : undefined, - on_finish: function() { - // Cleanup speech controls when task is complete - if (speechCleanup) { - speechCleanup(); - } - stopSpeech(); - } - }); - - return { - timeline: timeline - }; -} - -// ============================================================================ -// EXPORTS -// ============================================================================ - -export const timelineUnits = { - instructions: "Instructions for the flanker inhibitory control task", - practice: "Practice trials with feedback", - main: "Main flanker task trials", - completion: "Task completion screen" -}; - -export const utils = { - calculatePerformance, - createFlankerStim, - createPracticeStim, - - // TTS utilities - initializeTTS, - speakText, - stopSpeech, - getAvailableVoices, - speakInstructionPage, - speakTrialPrompt, - speakFeedback, - addSpeechControls -}; - -// Export text for external use -export { trial_text, instruction_pages, tts_config, tts_text } from './text'; - -// Default export -export default { createTimeline, timelineUnits, utils }; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/stimuli copy.ts b/packages/flanker-inhibitory/src/stimuli copy.ts deleted file mode 100644 index b9030501..00000000 --- a/packages/flanker-inhibitory/src/stimuli copy.ts +++ /dev/null @@ -1,43 +0,0 @@ -// ============================================================================ -// LAYERED SVG STIMULI -// ============================================================================ - -// Fish SVG (base layer) -const fish_svg = ` - - - - - - - - -`; - -// Arrow SVG (overlay layer) -const arrow_svg = ` - -`; - -// Default layered stimuli - fish with arrow overlay -export const layered_stimuli = [ - fish_svg, - arrow_svg -]; - -// Fish only -export const fish_only = [ - fish_svg -]; - -// Arrow only -export const arrow_only = [ - arrow_svg -]; - -// Custom example - you can add more layers -export const custom_example = [ - fish_svg, - arrow_svg, - // Add more layers here if needed -]; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css index b1e955fa..3ee61d23 100644 --- a/packages/flanker-inhibitory/src/styles.css +++ b/packages/flanker-inhibitory/src/styles.css @@ -148,8 +148,6 @@ width: var(--adjusted-svg-size, var(--svg-size)); height: var(--adjusted-svg-size, var(--svg-size)); display: block; - vertical-align: middle; - flex-shrink: 0; } /* Practice highlighting */ diff --git a/packages/flanker-inhibitory/test-alignment.js b/packages/flanker-inhibitory/test-alignment.js deleted file mode 100644 index 3ec5f49a..00000000 --- a/packages/flanker-inhibitory/test-alignment.js +++ /dev/null @@ -1,78 +0,0 @@ -// Quick test to verify the flipSVG function works correctly -const fs = require('fs'); - -// Read the stimuli file to get the fish SVG -const stimuliContent = fs.readFileSync('./src/stimuli.ts', 'utf8'); -const fishSvgMatch = stimuliContent.match(/const fish_svg = `([^`]+)`/s); -const fishSvg = fishSvgMatch ? fishSvgMatch[1] : ''; - -console.log('=== FISH SVG ANALYSIS ==='); -console.log('Original fish SVG has existing transform:', fishSvg.includes('transform=')); -console.log('ViewBox found:', fishSvg.match(/viewBox="([^"]+)"/)?.[1] || 'not found'); - -// Simulate the flipSVG function behavior -function flipSVG(svgString) { - if (!svgString || typeof svgString !== 'string') return svgString; - - // Check if SVG already contains a flip transform - const hasExistingTransform = svgString.includes('matrix(-1,') || - svgString.includes('matrix(-1 ') || - svgString.includes('scaleX(-1)') || - svgString.includes('scale(-1'); - - console.log('Has existing transform:', hasExistingTransform); - - // If already flipped, remove the flip transform to restore original orientation - if (hasExistingTransform) { - const result = svgString - .replace(/transform="matrix\(-1,\s*0,\s*0,\s*1,\s*0,\s*0\)"/g, '') - .replace(/transform="matrix\(-1\s+0\s+0\s+1\s+0\s+0\)"/g, '') - .replace(/transform="scaleX\(-1\)"/g, '') - .replace(/transform="scale\(-1,\s*1\)"/g, '') - .replace(/transform="scale\(-1\s+1\)"/g, ''); - - console.log('After removing existing transform, still has transform:', result.includes('transform=')); - return result; - } - - // Parse SVG to get dimensions for proper transform calculation - const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/); - const widthMatch = svgString.match(/width="([^"]+)"/); - - let translateX = 0; - if (viewBoxMatch) { - const viewBox = viewBoxMatch[1].split(/\s+/); - if (viewBox.length >= 3) { - translateX = parseFloat(viewBox[2]) || 0; // width from viewBox - } - } else if (widthMatch) { - translateX = parseFloat(widthMatch[1]) || 0; - } - - console.log('Calculated translateX:', translateX); - - // Apply proper SVG transform that maintains alignment - if (translateX > 0) { - // Insert transform into the SVG element itself for better precision - return svgString.replace( - /]*)>/, - `` - ).replace(/<\/svg>$/, ''); - } else { - // Fallback to CSS transform with inline-block container - return `${svgString}`; - } -} - -console.log('\n=== TESTING FLIP FUNCTION ==='); -const flippedOnce = flipSVG(fishSvg); -console.log('After first flip - has transform:', flippedOnce.includes('transform=')); - -const flippedTwice = flipSVG(flippedOnce); -console.log('After second flip - has transform:', flippedTwice.includes('transform=')); - -console.log('\n=== SUCCESS ==='); -console.log('The flipSVG function should now properly handle fish alignment by:'); -console.log('1. Detecting existing transforms in the fish SVG'); -console.log('2. Removing them when flipping back to original orientation'); -console.log('3. Using proper SVG transforms instead of CSS when flipping'); \ No newline at end of file diff --git a/packages/pattern-comparison-test/README.md b/packages/pattern-comparison-test/README.md deleted file mode 100644 index 69a12f1f..00000000 --- a/packages/pattern-comparison-test/README.md +++ /dev/null @@ -1,50 +0,0 @@ -# pattern-comparison-test - -## Overview - -An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same. - -## Loading - -### In browser - -```html - - - - - - - - - - - \ No newline at end of file diff --git a/packages/pattern-comparison-test/jest.config.js b/packages/pattern-comparison-test/jest.config.js deleted file mode 100644 index bd1aacef..00000000 --- a/packages/pattern-comparison-test/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/pattern-comparison-test/package.json b/packages/pattern-comparison-test/package.json deleted file mode 100644 index b868a28f..00000000 --- a/packages/pattern-comparison-test/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "@jspsych-timelines/pattern-comparison-test", - "version": "0.0.1", - "description": "An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same.", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.browser.min.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelinePatternComparisonTest" - }, - "repository": { - "type": "git", - "url": "https://github.com/farhat60/jspsych-timelines/", - "directory": "packages" - }, - "keywords": [ - "jsPsych" - ], - "author": { - "name": "A. Hunter Farhat", - "url": "github.com/farhat60" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/farhat60/jspsych-timelines//issues" - }, - "homepage": "https://github.com/jspsych/jspsych-timelines/packages/pattern-comparison-test/README.md", - "devDependencies": { - "jspsych": "^8.0.0", - "@jspsych/config": "^3.2.2", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - } - } \ No newline at end of file diff --git a/packages/pattern-comparison-test/src/index copy.ts b/packages/pattern-comparison-test/src/index copy.ts deleted file mode 100644 index 8138d9f9..00000000 --- a/packages/pattern-comparison-test/src/index copy.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { JsPsych } from "jspsych" - -export function createTimeline(jsPsych:JsPsych, {}){ - -} - -export const timelineUnits = {} - -export const utils = {} \ No newline at end of file diff --git a/packages/pattern-comparison-test/src/index.ts b/packages/pattern-comparison-test/src/index.ts deleted file mode 100644 index a9f27135..00000000 --- a/packages/pattern-comparison-test/src/index.ts +++ /dev/null @@ -1,632 +0,0 @@ -import { JsPsych } from "jspsych" - -interface PatternComparisonConfig { - numTrials?: number; - stimulusDuration?: number; - fixationDuration?: number; - numPracticeTrials?: number; - showFeedback?: boolean; - patterns?: Array<{ left: string; right: string; correct_response: string }>; - showPromptText?: boolean; - promptText?: string; - enableAudio?: boolean; - useMobileButtons?: boolean; - sameButtonText?: string; - differentButtonText?: string; - sameButtonKey?: string; - differentButtonKey?: string; -} - -// SVG patterns from SVG Repo styled with CSS -const svgShapes = [ - // Star - ` - - `, - // Heart - ` - - `, - // Diamond - ` - - `, - // Circle with pattern - ` - - - - - - - `, - // Flower - ` - - `, - // Triangle - ` - - `, - // Hexagon - ` - - `, - // Cross - ` - - ` -]; - -const colors = [ - '#FF6B6B', // Red - '#4ECDC4', // Teal - '#45B7D1', // Blue - '#96CEB4', // Green - '#FFEAA7', // Yellow - '#DDA0DD', // Plum - '#FFB347', // Orange - '#87CEEB', // Sky Blue - '#F0A3FF', // Bright Pink - '#0075DC' // Bright Blue -]; - -// Text-to-speech utility -function speakText(text: string, enableAudio: boolean = false) { - if (!enableAudio || !('speechSynthesis' in window)) return; - - // Cancel any ongoing speech - speechSynthesis.cancel(); - - const utterance = new SpeechSynthesisUtterance(text); - utterance.rate = 0.8; - utterance.pitch = 1; - utterance.volume = 0.8; - speechSynthesis.speak(utterance); -} - -function generateSVGPattern(shapeIndex?: number, colorIndex?: number) { - const shape = shapeIndex !== undefined ? svgShapes[shapeIndex] : svgShapes[Math.floor(Math.random() * svgShapes.length)]; - const color = colorIndex !== undefined ? colors[colorIndex] : colors[Math.floor(Math.random() * colors.length)]; - return `
${shape}
`; -} - -function generateDefaultPatterns() { - const patterns = []; - - // Generate same patterns (same shape and color) - for (let i = 0; i < 45; i++) { - const shapeIndex = Math.floor(Math.random() * svgShapes.length); - const colorIndex = Math.floor(Math.random() * colors.length); - const pattern = generateSVGPattern(shapeIndex, colorIndex); - - patterns.push({ - left: pattern, - right: pattern, - correct_response: 's' - }); - } - - // Generate different patterns - for (let i = 0; i < 45; i++) { - const leftShapeIndex = Math.floor(Math.random() * svgShapes.length); - const leftColorIndex = Math.floor(Math.random() * colors.length); - - let rightShapeIndex, rightColorIndex; - - // Randomly decide if we change shape, color, or both - const changeType = Math.random(); - if (changeType < 0.33) { - // Change only shape - rightShapeIndex = (leftShapeIndex + 1 + Math.floor(Math.random() * (svgShapes.length - 1))) % svgShapes.length; - rightColorIndex = leftColorIndex; - } else if (changeType < 0.66) { - // Change only color - rightShapeIndex = leftShapeIndex; - rightColorIndex = (leftColorIndex + 1 + Math.floor(Math.random() * (colors.length - 1))) % colors.length; - } else { - // Change both shape and color - rightShapeIndex = (leftShapeIndex + 1 + Math.floor(Math.random() * (svgShapes.length - 1))) % svgShapes.length; - rightColorIndex = (leftColorIndex + 1 + Math.floor(Math.random() * (colors.length - 1))) % colors.length; - } - - patterns.push({ - left: generateSVGPattern(leftShapeIndex, leftColorIndex), - right: generateSVGPattern(rightShapeIndex, rightColorIndex), - correct_response: 'd' - }); - } - - return patterns; -} - -export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig = {}) { - const { - numTrials = 90, - stimulusDuration = 30000, - fixationDuration = 500, - numPracticeTrials = 6, - showFeedback = true, - patterns = generateDefaultPatterns(), - showPromptText = true, - promptText = "Are these the same?", - enableAudio = false, - useMobileButtons = true, - sameButtonText = "Yes", - differentButtonText = "No", - sameButtonKey = 's', - differentButtonKey = 'd' - } = config; - - const timeline = []; - - // Common CSS for mobile responsiveness - const responsiveCSS = ` - - `; - - // Instructions - const instructionsText = ` - Pattern Comparison Test. In this test, you will see pairs of patterns displayed side by side. - Your task is to determine whether the two patterns are exactly the same or different. - Pay attention to both the shape and the color of the patterns. - `; - - const instructions = { - type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', - stimulus: ` - ${responsiveCSS} -
-

Pattern Comparison Test

-

In this test, you will see pairs of patterns displayed side by side.

-

Your task is to determine whether the two patterns are exactly the same or different.

-

Pay attention to both the shape and the color of the patterns.

-
-
-
-

Press '${sameButtonKey.toUpperCase()}' if the patterns are the SAME (identical shape AND color)

-

Press '${differentButtonKey.toUpperCase()}' if the patterns are DIFFERENT (different shape OR color)

-
-
-

Press "${sameButtonText}" if the patterns are the SAME (identical shape AND color)

-

Press "${differentButtonText}" if the patterns are DIFFERENT (different shape OR color)

-
-
-
-

Please respond as quickly and accurately as possible.

-

${useMobileButtons ? 'Tap "Continue" to start the practice trials' : 'Press any key to start the practice trials'}

-
- `, - ...(useMobileButtons ? { - choices: ['Continue'], - button_html: '' - } : { - choices: "ALL_KEYS" - }), - data: { task: 'pattern-comparison', phase: 'instructions' }, - on_start: function() { - if (enableAudio) { - setTimeout(() => speakText(instructionsText, enableAudio), 100); - } - } - }; - - // Practice instructions - const practiceInstructionsText = ` - Practice Trials. Let's start with a few practice trials to get familiar with the task. - You will receive feedback after each practice trial. - `; - - const practiceInstructions = { - type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', - stimulus: ` - ${responsiveCSS} -
-

Practice Trials

-

Let's start with a few practice trials to get familiar with the task.

-

You will receive feedback after each practice trial.

-

- Remember: Press '${sameButtonKey.toUpperCase()}' for SAME, '${differentButtonKey.toUpperCase()}' for DIFFERENT - Remember: Press "${sameButtonText}" for SAME, "${differentButtonText}" for DIFFERENT -

-

${useMobileButtons ? 'Tap "Begin" to start' : 'Press any key to begin'}

-
- `, - ...(useMobileButtons ? { - choices: ['Begin'], - button_html: '' - } : { - choices: "ALL_KEYS" - }), - data: { task: 'pattern-comparison', phase: 'practice-instructions' }, - on_start: function() { - if (enableAudio) { - setTimeout(() => speakText(practiceInstructionsText, enableAudio), 100); - } - } - }; - - // Practice trial - const practiceTrial = { - type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', - stimulus: jsPsych.timelineVariable('stimulus'), - ...(useMobileButtons ? { - choices: [sameButtonText, differentButtonText], - button_html: (choice: string, choice_index: number) => { - const buttonClass = choice_index === 0 ? 'same-button' : 'different-button'; - return ``; - } - } : { - choices: [sameButtonKey, differentButtonKey] - }), - data: jsPsych.timelineVariable('data'), - on_start: function() { - if (enableAudio && showPromptText) { - setTimeout(() => speakText(promptText, enableAudio), 100); - } - }, - on_finish: function(data: any) { - if (useMobileButtons) { - data.response = data.response === 0 ? sameButtonKey : differentButtonKey; - } - data.correct = data.response === data.correct_response; - } - }; - - // Practice feedback - const practiceFeedback = { - type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', - stimulus: function() { - const lastTrial = jsPsych.data.getLastTrialData().values()[0]; - if (lastTrial.correct) { - return ` - ${responsiveCSS} -
-

✓ Correct!

-

${useMobileButtons ? 'Tap "Continue" to proceed' : 'Press any key to continue'}

-
- `; - } else { - const correctKey = lastTrial.correct_response === sameButtonKey ? sameButtonText : differentButtonText; - return ` - ${responsiveCSS} -
-

✗ Incorrect

-

The correct answer was: ${correctKey}

-

${useMobileButtons ? 'Tap "Continue" to proceed' : 'Press any key to continue'}

-
- `; - } - }, - ...(useMobileButtons ? { - choices: ['Continue'], - button_html: '' - } : { - choices: "ALL_KEYS" - }), - trial_duration: useMobileButtons ? null : 2500, - data: { task: 'pattern-comparison', phase: 'practice-feedback' } - }; - - // Practice block - const practiceBlock = { - timeline: showFeedback ? [practiceTrial, practiceFeedback] : [practiceTrial], - timeline_variables: patterns.slice(0, numPracticeTrials).map(pattern => ({ - stimulus: createPatternStimulus(pattern, showPromptText, promptText), - data: { - task: 'pattern-comparison', - phase: 'practice', - correct_response: pattern.correct_response - } - })) - }; - - // Main test instructions - const testInstructionsText = ` - Main Test. Great! Now you'll begin the main test. - There will be no feedback, and you should work as quickly and accurately as possible. - `; - - const testInstructions = { - type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', - stimulus: ` - ${responsiveCSS} -
-

Main Test

-

Great! Now you'll begin the main test.

-

There will be no feedback, and you should work as quickly and accurately as possible.

-

- '${sameButtonKey.toUpperCase()}' for SAME (identical shape and color)
'${differentButtonKey.toUpperCase()}' for DIFFERENT (different shape or color)
- "${sameButtonText}" for SAME (identical shape and color)
"${differentButtonText}" for DIFFERENT (different shape or color)
-

-

${useMobileButtons ? 'Tap "Begin Test" to start' : 'Press any key to begin the test'}

-
- `, - ...(useMobileButtons ? { - choices: ['Begin Test'], - button_html: '' - } : { - choices: "ALL_KEYS" - }), - data: { task: 'pattern-comparison', phase: 'test-instructions' }, - on_start: function() { - if (enableAudio) { - setTimeout(() => speakText(testInstructionsText, enableAudio), 100); - } - } - }; - - // Fixation cross - const fixation = { - type: 'html-keyboard-response', - stimulus: `${responsiveCSS}
+
`, - choices: "NO_KEYS", - trial_duration: fixationDuration, - data: { task: 'pattern-comparison', phase: 'fixation' } - }; - - // Main test trial - const testTrial = { - type: useMobileButtons ? 'html-button-response' : 'html-keyboard-response', - stimulus: jsPsych.timelineVariable('stimulus'), - ...(useMobileButtons ? { - choices: [sameButtonText, differentButtonText], - button_html: (choice: string, choice_index: number) => { - const buttonClass = choice_index === 0 ? 'same-button' : 'different-button'; - return ``; - } - } : { - choices: [sameButtonKey, differentButtonKey] - }), - data: jsPsych.timelineVariable('data'), - on_start: function() { - if (enableAudio && showPromptText) { - setTimeout(() => speakText(promptText, enableAudio), 100); - } - }, - on_finish: function(data: any) { - if (useMobileButtons) { - data.response = data.response === 0 ? sameButtonKey : differentButtonKey; - } - data.correct = data.response === data.correct_response; - } - }; - - // Test block - const testBlock = { - timeline: fixationDuration > 0 ? [fixation, testTrial] : [testTrial], - timeline_variables: patterns.slice(numPracticeTrials, numPracticeTrials + numTrials).map(pattern => ({ - stimulus: createPatternStimulus(pattern, showPromptText, promptText), - data: { - task: 'pattern-comparison', - phase: 'test', - correct_response: pattern.correct_response - } - })), - randomize_order: true - }; - - // Build timeline - timeline.push(instructions); - - if (numPracticeTrials > 0) { - timeline.push(practiceInstructions, practiceBlock); - } - - timeline.push(testInstructions, testBlock); - - return timeline; -} - -function createPatternStimulus(pattern: { left: string; right: string }, showPromptText: boolean = true, promptText: string = "Are these the same?") { - return ` -
- ${showPromptText ? `
${promptText}
` : ''} -
-
-
Pattern 1
-
- ${pattern.left} -
-
-
-
Pattern 2
-
- ${pattern.right} -
-
-
-
-
-
- `; -} - -export const timelineUnits = { - instructions: 'instructions', - practiceInstructions: 'practice-instructions', - practice: 'practice', - testInstructions: 'test-instructions', - main: 'main' -} - -export const utils = { - generateSVGPattern, - generateDefaultPatterns, - createPatternStimulus, - speakText, - calculateAccuracy: (data: any[]) => { - const testTrials = data.filter(trial => trial.phase === 'test' || trial.phase === 'practice'); - const correct = testTrials.filter(trial => trial.correct).length; - return testTrials.length > 0 ? (correct / testTrials.length) * 100 : 0; - }, - calculateAverageRT: (data: any[]) => { - const testTrials = data.filter(trial => (trial.phase === 'test' || trial.phase === 'practice') && trial.correct); - if (testTrials.length === 0) return 0; - const rts = testTrials.map(trial => trial.rt); - return rts.reduce((sum, rt) => sum + rt, 0) / rts.length; - }, - getTrialsByPhase: (data: any[], phase: string) => { - return data.filter(trial => trial.phase === phase); - } -} - -// Export constants for external use -export const constants = { - svgShapes, - colors, - defaultConfig: { - numTrials: 90, - stimulusDuration: 30000, - fixationDuration: 500, - numPracticeTrials: 6, - showFeedback: true, - showPromptText: true, - promptText: "Are these the same?", - enableAudio: false, - useMobileButtons: true, - sameButtonText: "Yes", - differentButtonText: "No", - sameButtonKey: 's', - differentButtonKey: 'd' - } -} \ No newline at end of file diff --git a/packages/pattern-comparison-test/tsconfig.json b/packages/pattern-comparison-test/tsconfig.json deleted file mode 100644 index 33655f70..00000000 --- a/packages/pattern-comparison-test/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@jspsych/config/tsconfig.contrib.json", - "compilerOptions": { - "baseUrl": "." - }, - "include": [ - "src", - "../cli/src/build.js" - ] -} \ No newline at end of file diff --git a/packages/spatial-nback/package.json b/packages/spatial-nback/package.json index 523d604d..f00323f3 100644 --- a/packages/spatial-nback/package.json +++ b/packages/spatial-nback/package.json @@ -33,7 +33,7 @@ "typescript": "^5.0.2" }, "dependencies": { - "@jspsych-contrib/plugin-spatial-nback": "https://github.com/farhat60/jspsych-contrib/tree/main/", + "@jspsych-contrib/plugin-spatial-nback": "github:farhat60/jspsych-contrib", "@jspsych/plugin-instructions": "^2.1.0", "jsPsych": "npm:jspsych@^8.2.1" } diff --git a/packages/speeded-matching/examples/index.html b/packages/speeded-matching/examples/index.html index 8808914f..5ee986d3 100644 --- a/packages/speeded-matching/examples/index.html +++ b/packages/speeded-matching/examples/index.html @@ -26,8 +26,8 @@ num_trials: 8, // Number of test trials (shortened for demo) num_choices: 4, // Number of choices per trial enable_tts: true, // Enable text-to-speech - show_instructions: false, // Show instruction pages - show_practice: false, // Show practice round + show_instructions: true, // Show instruction pages + show_practice: true, // Show practice round trial_timeout: 10000, // 10 seconds per trial inter_trial_interval: 500 // 500ms between trials }; From 3071a0cc334b6cf8e1385305dc4b14677af0a6d7 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 8 Jul 2025 11:34:43 -0400 Subject: [PATCH 26/37] keeping only the respective package --- packages/arrow-flanker/CHANGELOG.md | 15 - packages/arrow-flanker/README.md | 13 - packages/arrow-flanker/examples/index.html | 28 - .../examples/load-from-unpkg.html | 28 - packages/arrow-flanker/jest.config.cjs | 1 - packages/arrow-flanker/package.json | 38 - packages/arrow-flanker/src/index.spec.ts | 11 - packages/arrow-flanker/src/index.ts | 65 -- packages/arrow-flanker/tsconfig.json | 7 - packages/false-memory/CHANGELOG.md | 15 - packages/false-memory/README.md | 49 -- packages/false-memory/example/index.html | 18 - packages/false-memory/jest.config.js | 1 - packages/false-memory/package.json | 39 - packages/false-memory/src/index.ts | 451 ---------- packages/false-memory/src/styles.css | 3 - packages/false-memory/tsconfig.json | 10 - packages/flanker-inhibitory/README.md | 128 --- .../docs/flanker-inhibitory.md | 242 ------ .../flanker-inhibitory/examples/index.html | 111 --- packages/flanker-inhibitory/jest.config.cjs | 1 - packages/flanker-inhibitory/package.json | 40 - packages/flanker-inhibitory/src/index.spec.ts | 750 ----------------- packages/flanker-inhibitory/src/index.ts | 768 ------------------ packages/flanker-inhibitory/src/stimuli.ts | 52 -- packages/flanker-inhibitory/src/styles.css | 416 ---------- packages/flanker-inhibitory/src/text.ts | 152 ---- packages/flanker-inhibitory/tsconfig.json | 10 - .../.claude/settings.local.json | 8 - packages/pattern-comparison-task/README.md | 76 -- .../docs/pattern-comparison-task.md | 83 -- .../examples/index copy.html | 35 - .../examples/index.html | 41 - .../pattern-comparison-task/jest.config.cjs | 1 - packages/pattern-comparison-task/package.json | 39 - .../src/index copy 4.ts | 266 ------ .../pattern-comparison-task/src/index.spec.ts | 414 ---------- packages/pattern-comparison-task/src/index.ts | 314 ------- .../pattern-comparison-task/src/styles.css | 159 ---- .../src/test-categories.ts | 184 ----- packages/pattern-comparison-task/src/text.ts | 60 -- .../pattern-comparison-task/tsconfig.json | 10 - packages/spatial-cueing/CHANGELOG.md | 16 - packages/spatial-cueing/README.md | 51 -- packages/spatial-cueing/examples/index.html | 17 - packages/spatial-cueing/jest.config.js | 1 - packages/spatial-cueing/package.json | 36 - packages/spatial-cueing/src/index.ts | 377 --------- packages/spatial-cueing/src/styles.css | 35 - packages/spatial-cueing/tsconfig.json | 10 - 50 files changed, 5695 deletions(-) delete mode 100644 packages/arrow-flanker/CHANGELOG.md delete mode 100644 packages/arrow-flanker/README.md delete mode 100644 packages/arrow-flanker/examples/index.html delete mode 100644 packages/arrow-flanker/examples/load-from-unpkg.html delete mode 100644 packages/arrow-flanker/jest.config.cjs delete mode 100644 packages/arrow-flanker/package.json delete mode 100644 packages/arrow-flanker/src/index.spec.ts delete mode 100644 packages/arrow-flanker/src/index.ts delete mode 100644 packages/arrow-flanker/tsconfig.json delete mode 100644 packages/false-memory/CHANGELOG.md delete mode 100644 packages/false-memory/README.md delete mode 100644 packages/false-memory/example/index.html delete mode 100644 packages/false-memory/jest.config.js delete mode 100644 packages/false-memory/package.json delete mode 100644 packages/false-memory/src/index.ts delete mode 100644 packages/false-memory/src/styles.css delete mode 100644 packages/false-memory/tsconfig.json delete mode 100644 packages/flanker-inhibitory/README.md delete mode 100644 packages/flanker-inhibitory/docs/flanker-inhibitory.md delete mode 100644 packages/flanker-inhibitory/examples/index.html delete mode 100644 packages/flanker-inhibitory/jest.config.cjs delete mode 100644 packages/flanker-inhibitory/package.json delete mode 100644 packages/flanker-inhibitory/src/index.spec.ts delete mode 100644 packages/flanker-inhibitory/src/index.ts delete mode 100644 packages/flanker-inhibitory/src/stimuli.ts delete mode 100644 packages/flanker-inhibitory/src/styles.css delete mode 100644 packages/flanker-inhibitory/src/text.ts delete mode 100644 packages/flanker-inhibitory/tsconfig.json delete mode 100644 packages/pattern-comparison-task/.claude/settings.local.json delete mode 100644 packages/pattern-comparison-task/README.md delete mode 100644 packages/pattern-comparison-task/docs/pattern-comparison-task.md delete mode 100644 packages/pattern-comparison-task/examples/index copy.html delete mode 100644 packages/pattern-comparison-task/examples/index.html delete mode 100644 packages/pattern-comparison-task/jest.config.cjs delete mode 100644 packages/pattern-comparison-task/package.json delete mode 100644 packages/pattern-comparison-task/src/index copy 4.ts delete mode 100644 packages/pattern-comparison-task/src/index.spec.ts delete mode 100644 packages/pattern-comparison-task/src/index.ts delete mode 100644 packages/pattern-comparison-task/src/styles.css delete mode 100644 packages/pattern-comparison-task/src/test-categories.ts delete mode 100644 packages/pattern-comparison-task/src/text.ts delete mode 100644 packages/pattern-comparison-task/tsconfig.json delete mode 100644 packages/spatial-cueing/CHANGELOG.md delete mode 100644 packages/spatial-cueing/README.md delete mode 100644 packages/spatial-cueing/examples/index.html delete mode 100644 packages/spatial-cueing/jest.config.js delete mode 100644 packages/spatial-cueing/package.json delete mode 100644 packages/spatial-cueing/src/index.ts delete mode 100644 packages/spatial-cueing/src/styles.css delete mode 100644 packages/spatial-cueing/tsconfig.json diff --git a/packages/arrow-flanker/CHANGELOG.md b/packages/arrow-flanker/CHANGELOG.md deleted file mode 100644 index f53204c9..00000000 --- a/packages/arrow-flanker/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# @jspsych-timelines/arrow-flanker - -## 0.2.0 - -### Minor Changes - -- 22c0236: delete -task suffix - -## 0.1.0 - -### Minor Changes - -- d6a0e3b: upgrade jspsych depedencies to v8 -- 0de5056: update all jspsych dependencies to v8 -- 0b18198: upgrade to use jspsych v8 diff --git a/packages/arrow-flanker/README.md b/packages/arrow-flanker/README.md deleted file mode 100644 index ce7ee295..00000000 --- a/packages/arrow-flanker/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# arrow-flanker - -## Overview - -This timeline shows a sequence of arrow flanker trials. Participants are supposed to respond to the arrow in the middle of the screen and ignore the flankers. Half of the trials will be congruent (flankers match the target) and half incongruent. - -## Functions - -### `createTimeline` - -### `timelineUnits` - -### `utils` \ No newline at end of file diff --git a/packages/arrow-flanker/examples/index.html b/packages/arrow-flanker/examples/index.html deleted file mode 100644 index 84cb5f2e..00000000 --- a/packages/arrow-flanker/examples/index.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/arrow-flanker/examples/load-from-unpkg.html b/packages/arrow-flanker/examples/load-from-unpkg.html deleted file mode 100644 index 75f63496..00000000 --- a/packages/arrow-flanker/examples/load-from-unpkg.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/arrow-flanker/jest.config.cjs b/packages/arrow-flanker/jest.config.cjs deleted file mode 100644 index 6ac19d5c..00000000 --- a/packages/arrow-flanker/jest.config.cjs +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); diff --git a/packages/arrow-flanker/package.json b/packages/arrow-flanker/package.json deleted file mode 100644 index a61bcad6..00000000 --- a/packages/arrow-flanker/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "@jspsych-timelines/arrow-flanker", - "version": "0.2.0", - "description": "Arrow flanker task for jsPsych", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.global.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineArrowFlankerTask" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/jspsych/jspsych-timelines.git" - }, - "keywords": [ - "jsPsych" - ], - "author": { - "name": "Josh de Leeuw", - "url": "https://github.com/jodeleeuw" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/jspsych/jspsych-timelines/issues" - }, - "homepage": "https://github.com/jspsych/jspsych-timelines/packages/arrow-flanker#readme", - "peerDependencies": { - "jspsych": "^8.0.1" - }, - "dependencies": { - "@jspsych/plugin-html-keyboard-response": "^2.0.0" - }, - "devDependencies": { - "tsup": "^8.0.1", - "typescript": "^5.5.4" - } -} \ No newline at end of file diff --git a/packages/arrow-flanker/src/index.spec.ts b/packages/arrow-flanker/src/index.spec.ts deleted file mode 100644 index 29debd29..00000000 --- a/packages/arrow-flanker/src/index.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { JsPsych, initJsPsych } from "jspsych"; -import { createTimeline } from "."; - -describe("createTimeline", () => { - it("should return a timeline", () => { - const jsPsych = initJsPsych(); - - const timeline = createTimeline(jsPsych); - expect(timeline).toBeDefined(); - }); -}); \ No newline at end of file diff --git a/packages/arrow-flanker/src/index.ts b/packages/arrow-flanker/src/index.ts deleted file mode 100644 index 120bc103..00000000 --- a/packages/arrow-flanker/src/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { JsPsych } from "jspsych"; -import jsPsychHtmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response' - -const left_arrow = ``; -const right_arrow = ``; - -function createFlankerStim(direction, congruent) { - let html = `
`; - if (congruent) { - if (direction === "left") { - html += ` ${left_arrow} ${left_arrow} ${left_arrow} ${left_arrow} ${left_arrow} `; - } else { - html += ` ${right_arrow} ${right_arrow} ${right_arrow} ${right_arrow} ${right_arrow} `; - } - } else { - if (direction === "left") { - html += ` ${right_arrow} ${right_arrow} ${left_arrow} ${right_arrow} ${right_arrow} `; - } else { - html += ` ${left_arrow} ${left_arrow} ${right_arrow} ${left_arrow} ${left_arrow} `; - } - } - html += `
`; - return html; -} - -export function createTimeline(jsPsych: JsPsych, { - fixation_duration = 500, - n = 12, -} : { - fixation_duration?: number, - n?: number, -} = {}){ - - const timeline_variables = [ - {direction: 'left', congruent: true}, - {direction: 'left', congruent: false}, - {direction: 'right', congruent: true}, - {direction: 'right', congruent: false}, - ] - - const trials = jsPsych.randomization.repeat(timeline_variables, Math.floor(n/4)).concat(jsPsych.randomization.sampleWithoutReplacement(timeline_variables, n%4)); - - const fixation = { - type: jsPsychHtmlKeyboardResponse, - stimulus: '+', - choices: "NONE", - trial_duration: fixation_duration, - } - - const flanker = { - type: jsPsychHtmlKeyboardResponse, - stimulus: ()=>{ - return createFlankerStim(jsPsych.timelineVariable('direction'), jsPsych.timelineVariable('congruent')) - }, - choices: ['ArrowLeft', 'ArrowRight'], - } - - const flanker_task = { - timeline: [fixation, flanker], - timeline_variables: trials, - randomize_order: true - } - - return flanker_task; -} \ No newline at end of file diff --git a/packages/arrow-flanker/tsconfig.json b/packages/arrow-flanker/tsconfig.json deleted file mode 100644 index 3eabd0c2..00000000 --- a/packages/arrow-flanker/tsconfig.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "@jspsych/config/tsconfig.contrib.json", - "compilerOptions": { - "baseUrl": "." - }, - "include": ["src"] -} diff --git a/packages/false-memory/CHANGELOG.md b/packages/false-memory/CHANGELOG.md deleted file mode 100644 index 53a50af8..00000000 --- a/packages/false-memory/CHANGELOG.md +++ /dev/null @@ -1,15 +0,0 @@ -# @jspsych-timelines/false-memory - -## 0.2.0 - -### Minor Changes - -- 22c0236: delete -task suffix - -## 0.1.0 - -### Minor Changes - -- d6a0e3b: upgrade jspsych depedencies to v8 -- 0de5056: update all jspsych dependencies to v8 -- 0b18198: upgrade to use jspsych v8 diff --git a/packages/false-memory/README.md b/packages/false-memory/README.md deleted file mode 100644 index d23edb34..00000000 --- a/packages/false-memory/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# false-memory - -## Overview - -A timeline for the generic DRM false memory task - -## Loading - -### In browser - -```html - - - - - - - - - - - diff --git a/packages/false-memory/jest.config.js b/packages/false-memory/jest.config.js deleted file mode 100644 index bd1aacef..00000000 --- a/packages/false-memory/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/false-memory/package.json b/packages/false-memory/package.json deleted file mode 100644 index 920bce7e..00000000 --- a/packages/false-memory/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@jspsych-timelines/false-memory", - "version": "0.2.0", - "description": "False memory task for jsPsych", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.global.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineFalseMemoryTask" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/jspsych/jspsych-timelines.git" - }, - "keywords": [ - "jsPsych" - ], - "author": { - "name": "Cherrie Chang", - "url": "https://github.com/cchang-vassar" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/jspsych/jspsych-timelines/issues" - }, - "homepage": "https://github.com/jspsych/jspsych-timelines/packages/false-memory#readme", - "peerDependencies": { - "jspsych": "^8.0.1" - }, - "dependencies": { - "@jspsych/plugin-html-keyboard-response": "^2.0.0", - "@jspsych/plugin-survey-text": "^2.0.0" - }, - "devDependencies": { - "tsup": "^8.2.4", - "typescript": "^5.5.4" - } -} diff --git a/packages/false-memory/src/index.ts b/packages/false-memory/src/index.ts deleted file mode 100644 index 42d6a842..00000000 --- a/packages/false-memory/src/index.ts +++ /dev/null @@ -1,451 +0,0 @@ -import { JsPsych } from "jspsych"; -import jsPsychHtmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response'; -import jsPsychSurveyText from '@jspsych/plugin-survey-text'; - -/* constants */ -const NUM_LIST_CEILING: number = 12; -const NUM_WORDS_IN_LIST_CEILING: number = 15; -const ALL_LIST_KENT_ROSANOFF_INDEX: number[] = [32, 45, 97, 89, 26, 53, 33, 42, 63, 44, 7, 85]; //randomly chosen -const ALL_LIST_INDEX_ARRAY: number[] = Array.from({length: 12}, (_, index) => index + 1); -const KEYPRESS_TO_NEXT_SLIDE: String = 'Press any key to move to the next slide.'; - -const ALL_LIST = [ - { - lure: "citizen", - word_list: ["USA", "man", "person", "American", "country", "alien", "people", "state", "votes", "patriot", "flag", "voter", "foreigner", "government", "America"] - }, - { - lure: "trouble", - word_list: ["bad", "shooter", "worry", "danger", "sorrow", "fear", "school", "problem", "police", "fight", "sad", "happy", "difficulty", "peace", "hard"] - }, - { - lure: "king", - word_list: ["queen", "England", "crown", "pin", "ruler", "Kong", "throne", "cards", "Tut", "rule", "subjects", "chess", "royal", "power", "county"] - }, - { - lure: "mutton", - word_list: ["lamb", "sheep", "meat", "chops", "beef", "veal", "collar", "stew", "coat", "steak", "fur", "pork", "dog", "wool", "animal"] - }, - { - lure: "whistle", - word_list: ["stop", "train", "noise", "sing", "blow", "tune", "sound", "shrill", "lips", "wolf", "loud", "call", "mouth", "cop", "horn"] - }, - { - lure: "street", - word_list: ["Avenue", "oars", "lights", "city", "alley", "walk", "sign", "houses", "corner", "sidewalk", "town", "number", "scene", "drive", "name"] - }, - { - lure: "foot", - word_list: ["shoes", "hand", "toes", "soldier", "feet", "ball", "ankle", "arm", "sore", "inches", "boot", "yard", "socks", "head", "hurt"] - }, - { - lure: "working", - word_list: ["loafing", "sleeping", "playing", "resting", "labor", "lazy", "job", "tired", "hours", "rest", "fast", "busy", "pay", "doing", "studying"] - }, - { - lure: "stomach", - word_list: ["ache", "intestine", "ulcer", "body", "organ", "pains", "hunger", "belly", "pump", "full", "digestion", "gut", "sick", "abdomen", "empty"] - }, - { - lure: "table", - word_list: ["chair", "desk", "top", "legs", "eat", "cloth", "dishes", "wood", "dinner", "tennis", "maple", "door", "cup", "room", "fork"] - }, - { - lure: "bath", - word_list: ["clean", "soap", "tub", "wash", "shower", "dirty", "dirt", "warm", "cleanliness", "hot", "towel", "cold", "bathtub", "bubble", "bathe"] - }, - { - lure: "lion", - word_list: ["tiger", "roar", "cub", "den", "zoo", "jungle", "cage", "tamer", "mouse", "bear", "hearted", "mane", "Africa", "Leo", "growl"] - } -]; - -/* internal vars */ -var list_studied = []; -var test_stimuli = []; -var weights: number[] = []; - -var list_num = 1; -var word_num = 0; - -/* internal functions */ -// helper function that gives error message if the studied and unstudied lists overlap -function checkListOverflowOverlap(list_studied_index_array: number[], list_unstudied_index_array: number[]) { - if ((list_studied_index_array.length + list_unstudied_index_array.length) > NUM_LIST_CEILING) { - //TODO: some kind of warning message - }; - let listIntersection = list_studied_index_array.filter(x => list_unstudied_index_array.includes(x)); - if (listIntersection.length > 0) { - //TODO: some kind of warning message - }; -}; - -// helper function to check if input number of words in lists exceed ceiling -function checkNumWordsOverflow(input_num_words: number) { - if (input_num_words > NUM_WORDS_IN_LIST_CEILING) { - //TODO: some kind of warning message - }; -}; - -function pushTestStimuliStudied(num_studied_lists: number, num_words_in_list: number, list_studied_index_array: number[], test_all_studied_lures: boolean){ - for (let i = 0; i < num_studied_lists; i++){ - var studied_list_to_add = ALL_LIST[list_studied_index_array[i] - 1]; - list_studied.push(studied_list_to_add); - test_stimuli.push({ - stimulus: studied_list_to_add.lure, - correct_response: 'j' - }); - weights.push(test_all_studied_lures ? 99999 : 1); - for (let j = 0; j < num_words_in_list; j++){ - test_stimuli.push({ - stimulus: studied_list_to_add.word_list[j], - correct_response: 'f' - }) - weights.push(1); - } - }; -}; - -function pushTestStimuliUnstudied(num_unstudied_lists: number, num_words_in_list: number, list_unstudied_index_array: number[], test_all_unstudied_lures: boolean){ - for (let i = 0; i < num_unstudied_lists; i++){ - var unstudied_list_to_add = ALL_LIST[list_unstudied_index_array[i] - 1]; - test_stimuli.push({ - stimulus: unstudied_list_to_add.lure, - correct_response: 'j' - }); - weights.push(test_all_unstudied_lures ? 99999 : 1); - for (let j = 0; j < num_words_in_list; j++){ - test_stimuli.push({ - stimulus: unstudied_list_to_add.word_list[j], - correct_response: 'f' - }) - weights.push(1); - }; - }; -}; - -/* timeline component generating functions */ -// generate start instructions -function showStartInstruction(num_studied_lists: number, num_words_in_list: number, recall_period: number) { - const stimulus = `

This is a demo of the DRM false memory experiment paradigm.

` - + `

In the following trials, you will learn` - + num_studied_lists - + `lists of words, with each list containing` - + num_words_in_list - + `words.
` - + `The words in each list will be presented one by one.

` - + `

After each list is presented, you will be given an empty text box and` - + (recall_period / 1000) - + `seconds to freely recall as many words you can remember from the list as possible.` - + `What you write in this text box will not be recorded, but please do not try to capture the words you recall in this box by taking a screenshot, writing them down on paper, etc.

` - + `

Try to remember as many words from all the lists as possible!

` - + `

` + KEYPRESS_TO_NEXT_SLIDE + `

`; - - const introduction = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - return stimulus; - } - }; - - return introduction; -}; - -// generate a study trial for a list -function createListStudyTimelineUnit( - jsPsych: JsPsych, - num_words_in_list: number, - word_show_period: number, - list_title_card_period: number, - list_end_card_period: number, - recall_period: number, - allow_recall: boolean) { - - // study through list of words in a single list - const study_trial = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - return "

" + jsPsych.timelineVariable("word_list")[word_num] + "

"; - }, - choices: "NO_KEYS", - trial_duration: word_show_period - }; - - const word_study_timeline = { - timeline: [study_trial], - timeline_variables: jsPsych.timelineVariable("word_list")[list_num], - loop_function: () => { - word_num++; - return word_num < num_words_in_list; - } - }; - - // wrap single list study with title cards and recall period - const list_start_card = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - return "

List " + list_num + "

"; - }, - choices: "NO_KEYS", - trial_duration: list_title_card_period - }; - - const list_end_card = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - return "

End of List " + list_num + "

"; - }, - choices: "NO_KEYS", - on_finish: () => { - list_num++; - word_num = 0; - }, - trial_duration: list_end_card_period - }; - - const recall = { - type: jsPsychSurveyText, - on_start: () => { - jsPsych.pluginAPI.setTimeout(() => { - let element : HTMLFormElement = document.querySelector('#jspsych-survey-text-form'); - element.requestSubmit(); - }, recall_period) - }, - questions: [ - { prompt: 'Use this space to recall as many words as you can from the previous list.
Please do not try to capture what you type.', rows: 5 } - ] - }; - - const single_list_study_timeline = { - timeline: [ - list_start_card, - word_study_timeline, - list_end_card, - (allow_recall? recall : null) - ] - }; - - return single_list_study_timeline; -}; - -// generate a series of study trials for a list of lists -function createListStudyTimeline( - jsPsych: JsPsych, - num_studied_lists: number, - num_words_in_list: number, - word_show_period: number, - list_title_card_period: number, - list_end_card_period: number, - recall_period: number, - allow_recall: boolean) { - const all_list_study_timeline = { - timeline: [createListStudyTimelineUnit( - jsPsych, - num_words_in_list, - word_show_period, - list_title_card_period, - list_end_card_period, - recall_period, - allow_recall - )], - timeline_variables: list_studied, - randomize_order: true, - sample: { - type: "without-replacement", - size: num_studied_lists - } - }; - - return all_list_study_timeline; -}; - -// generate instruction for break section -function showBreakTimelineInstruction(break_period: number) { - const break_instruction = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - var stimulus = `

That's all the lists!

` - + `

Now, take a short ` - + (break_period / 1000 / 60) - + ` minute break.
` - + `The experiment will automatically start after the timer reaches 0.` - + `

` - + KEYPRESS_TO_NEXT_SLIDE - + `

`; - - return stimulus; - } - }; -} - -// generate a break timer -function createBreakTimeline(break_period: number) { - const break_timer = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - var break_period_min = Math.floor(break_period / 60000); - var break_period_s = ((break_period / 1000) % 60); - return "

" + break_period_min + ":" + ((break_period_s < 10) ? "0" : "") + break_period_s + "

"; - }, - choices: "NO_KEYS", - trial_duration: 1000 - }; - - const break_timer_wrapper_timeline = { - timeline: [break_timer], - loop_function: () => { - break_period -= 1000; - return break_period >= 0; - } - }; - - const break_timeline = { - timeline: [ - break_timer_wrapper_timeline - ] - }; - - return break_timeline; -}; - -// function that generates the instructions for the test section -function showTestTimelineInstruction() { - const test_instruction = { - type: jsPsychHtmlKeyboardResponse, - stimulus: `
-

Welcome back!

-

Now, you will be shown a series of words.
- Some are words that were in the lists you just studied, and some are not.
- For each word, press 'f' if you remember seeing it in one of the lists;
- and press 'j' if you do not.

-

${KEYPRESS_TO_NEXT_SLIDE}

` - }; - - return test_instruction; -} - -// generate a series of test trials for all studied/unstudied lures + words -function createTestTimeline(jsPsych: JsPsych, test_with_replacement: boolean, num_tests: number) { - const test_trial_timeline = { - timeline: [ - { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - return "

" + jsPsych.timelineVariable("stimulus") + "

"; - }, - choices: ['f', 'j'], - data: { - task: "test_response", - correct_response: jsPsych.timelineVariable("correct_response"), - }, - on_finish: function (data) { - data.correct = jsPsych.pluginAPI.compareKeys( - data.response, - data.correct_response - ); - } - } - ], - timeline_variables: test_stimuli, - sample: { - type: (test_with_replacement ? 'with-replacement' : 'without-replacement'), - size: num_tests, - weights: weights - } - }; - - return test_trial_timeline; -}; - -// generate the end instruction -function showEndInstruction() { - const end_experiment_instruction = { - type: jsPsychHtmlKeyboardResponse, - stimulus: `
-

That's the end of the experiment!

-

Thank you so much for participating in this demo of the DRM false memory experiment.
- Goodbye!

- ` - }; - - return end_experiment_instruction; -} - -// generate entire false memory task timeline -export function createTimeline( - jsPsych: JsPsych, { - list_studied_index_array = [1, 2, 3, 4, 5, 6], // how to give option of letting these take the default value? - list_unstudied_index_array = [7, 8, 9, 10, 11, 12], - num_words_in_list = NUM_WORDS_IN_LIST_CEILING, - num_tests = 90, - list_title_card_period = 3000, - list_end_card_period = 3000, - word_show_period = 2000, - recall_period = 30000, - break_period = 120000, - test_all_studied_lures = true, - test_all_unstudied_lures = true, - allow_recall = true, - test_with_replacement = false - }: { - list_studied_index_array?: number[], - list_unstudied_index_array?: number[], - num_words_in_list?: number, - num_tests?: number, - list_title_card_period?: number, - list_end_card_period?: number, - word_show_period?: number, - recall_period?: number, - break_period?: number, - test_all_studied_lures?: boolean, - test_all_unstudied_lures?: boolean, - allow_recall?: boolean, - test_with_replacement?: boolean - } = {}) -{ - var num_studied_lists: number = list_studied_index_array.length; - var num_unstudied_lists: number = list_unstudied_index_array.length; - var num_tests: number = (num_studied_lists + num_unstudied_lists) * (num_words_in_list + 1); - - checkListOverflowOverlap(list_studied_index_array, list_unstudied_index_array); - checkNumWordsOverflow(num_words_in_list); - - pushTestStimuliStudied(num_studied_lists, num_words_in_list, list_studied_index_array, test_all_studied_lures); - pushTestStimuliUnstudied(num_unstudied_lists, num_words_in_list, list_unstudied_index_array, test_all_unstudied_lures); - - const false_memory_timeline = { - timeline: [ - createListStudyTimeline( - jsPsych, - num_studied_lists, - num_words_in_list, - word_show_period, - list_title_card_period, - list_end_card_period, - recall_period, - allow_recall), - createBreakTimeline(break_period), - createTestTimeline(jsPsych, test_with_replacement, num_tests), - ] - }; - - return false_memory_timeline; -}; - -export const timelineUnits = { - createListStudyTimeline, - createListStudyTimelineUnit, - createBreakTimeline, - createTestTimeline -}; - -export const utils = { - checkListOverflowOverlap, - checkNumWordsOverflow, - pushTestStimuliStudied, - pushTestStimuliUnstudied, - showStartInstruction, - showBreakTimelineInstruction, - showTestTimelineInstruction, - showEndInstruction -} \ No newline at end of file diff --git a/packages/false-memory/src/styles.css b/packages/false-memory/src/styles.css deleted file mode 100644 index 69b2949d..00000000 --- a/packages/false-memory/src/styles.css +++ /dev/null @@ -1,3 +0,0 @@ -.jspsych-false-memory-instruction { - max-width: 50vw; -} \ No newline at end of file diff --git a/packages/false-memory/tsconfig.json b/packages/false-memory/tsconfig.json deleted file mode 100644 index 33655f70..00000000 --- a/packages/false-memory/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@jspsych/config/tsconfig.contrib.json", - "compilerOptions": { - "baseUrl": "." - }, - "include": [ - "src", - "../cli/src/build.js" - ] -} \ No newline at end of file diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md deleted file mode 100644 index e28b96f6..00000000 --- a/packages/flanker-inhibitory/README.md +++ /dev/null @@ -1,128 +0,0 @@ -# flanker-inhibitory - -## Overview - -The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and selective attention based on the NIH Toolbox Flanker task. Participants respond to the direction of a central stimulus while ignoring flanking stimuli that may be congruent or incongruent with the target. - -## Loading - -### In browser - -```html - -``` - -### Via NPM - -```bash -npm install @jspsych-timelines/flanker-inhibitory -``` - -```js -import { createTimeline, timelineUnits, utils } from "@jspsych-timelines/flanker-inhibitory" -``` - -## Compatibility - -`@jspsych-timelines/flanker-inhibitory` requires jsPsych v8.0.0 or later. - -## Documentation - -### createTimeline - -#### jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { *options* }) ⇒ timeline - -Creates a complete flanker task timeline with instructions, practice trials, and main task trials. The task uses layered fish and arrow stimuli by default, with configurable parameters for customization. - -The following parameters can be specified in the **options** parameter. - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| stimuli_type | string | 'layered' | Type of stimuli to use: 'fish', 'arrow', 'layered', or 'custom' | -| svg | array | undefined | Override parameter - array of SVGs to layer (should be right-facing; left version auto-generated) | -| stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd ≥3) | -| fixation_duration | number | 500 | Duration of fixation cross in milliseconds | -| show_instructions | boolean | true | Whether to show instruction screens | -| show_practice | boolean | true | Whether to include practice trials | -| num_practice | number | 8 | Number of practice trials | -| num_trials | number | 20 | Number of main task trials | -| enable_tts | boolean | false | Enable text-to-speech functionality | -| tts_voice | string | '' | Voice name for TTS (empty uses default) | -| tts_rate | number | 1.0 | Speech rate (0.1 to 10) | -| tts_pitch | number | 1.0 | Speech pitch (0 to 2) | -| tts_volume | number | 1.0 | Speech volume (0 to 1) | -| tts_lang | string | 'en-US' | Language code for TTS | -| speak_instructions | boolean | true | Speak instruction page content | -| speak_prompts | boolean | true | Speak trial prompts | -| speak_feedback | boolean | true | Speak feedback messages | -| auto_speak | boolean | true | Auto-start speaking when content loads | - -#### Example Usage - -```js -const jsPsych = initJsPsych(); - -// Basic usage with defaults -const timeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych); - -// Custom configuration -const customTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - stimuli_type: 'fish', - stimuli_amount: 7, - num_trials: 40, - fixation_duration: 750 -}); - -// Using custom SVG override (provide right-facing SVGs) -const svgTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - svg: [ - '...right-facing base layer...', - '...right-facing overlay layer...' - ], - stimuli_amount: 9 - // Left-facing versions will be automatically generated by flipping -}); - -// With TTS enabled -const ttsTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - enable_tts: true, - speak_instructions: true, - speak_prompts: true, - speak_feedback: true, - tts_rate: 0.9, - tts_lang: 'en-US' -}); - -jsPsych.run([timeline]); -``` - -### timelineUnits - -Object containing identifiers for different phases of the task: -- `instructions`: Instruction screens -- `practice`: Practice trials -- `main`: Main task trials -- `completion`: Task completion screen - -### utils - -Utility functions for working with flanker stimuli and data: -- `calculatePerformance(data)`: Calculate accuracy and mean RT from trial data -- `createFlankerStim(direction, congruent, stimuli, stimuli_amount)`: Generate flanker stimulus HTML -- `createPracticeStim(direction, congruent, stimuli, stimuli_amount)`: Generate practice stimulus with highlighting - -## Features - -- **Adaptive stimuli**: Fish stimuli for younger children, arrows for older children, or layered combinations -- **Configurable difficulty**: Adjust number of flanker stimuli (3, 5, 7, 9, etc.) -- **Mobile-friendly**: Touch-optimized button interfaces -- **Practice mode**: Highlighted center stimulus for learning -- **Multiple input types**: Support for SVG arrays, custom stimuli objects, or simple overrides -- **Automatic flipping**: Single right-facing SVG automatically generates left-facing version -- **Layered SVGs**: Stack multiple SVG elements for complex stimuli -- **Text-to-Speech**: Optional TTS support for accessibility and younger participants -- **Speech controls**: Space bar to skip speech, configurable voice settings - -## Author / Citation - -Hunter Farhat \ No newline at end of file diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md deleted file mode 100644 index 0d9a97e0..00000000 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ /dev/null @@ -1,242 +0,0 @@ -# flanker-inhibitory - -The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and selective attention. Based on the NIH Toolbox Flanker task, participants must respond to the direction of a central stimulus while ignoring flanking stimuli that may be congruent (pointing the same direction) or incongruent (pointing the opposite direction). - -## Parameters - -### Configuration Parameters - -Configuration parameters can be passed when calling `createTimeline()`: - -```js -const timeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - stimuli_type: 'fish', - stimuli_amount: 7, - num_trials: 40 -}); -``` - -Parameter | Type | Default Value | Description -----------|------|---------------|------------ -stimuli_type | string | 'layered' | Type of stimuli: 'fish', 'arrow', 'layered' (fish + arrow), or 'custom' -svg | array | undefined | Override parameter - array of right-facing SVG strings to layer (left version auto-generated) -stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd number ≥3) -fixation_duration | number | 500 | Duration of fixation cross display in milliseconds -show_instructions | boolean | true | Whether to display instruction screens before the task -show_practice | boolean | true | Whether to include practice trials with feedback -num_practice | number | 8 | Number of practice trials to present -num_trials | number | 20 | Number of main task trials to present -enable_tts | boolean | false | Enable text-to-speech functionality -tts_voice | string | '' | Voice name for TTS (empty string uses default system voice) -tts_rate | number | 1.0 | Speech rate (0.1 to 10, where 1.0 is normal speed) -tts_pitch | number | 1.0 | Speech pitch (0 to 2, where 1.0 is normal pitch) -tts_volume | number | 1.0 | Speech volume (0 to 1, where 1.0 is maximum volume) -tts_lang | string | 'en-US' | Language code for speech synthesis -speak_instructions | boolean | true | Whether to speak instruction page content aloud -speak_prompts | boolean | true | Whether to speak trial prompts aloud -speak_feedback | boolean | true | Whether to speak feedback messages aloud -auto_speak | boolean | true | Whether to automatically start speaking when content loads - -### Stimulus Configuration Examples - -```js -// Use fish stimuli only -const fishConfig = { stimuli_type: 'fish' }; - -// Use custom right-facing SVG with 7 total stimuli -const customConfig = { - svg: ['...right-facing custom svg...'], - stimuli_amount: 7 - // Left-facing version automatically generated -}; - -// Use layered stimuli with custom amounts -const layeredConfig = { - stimuli_type: 'layered', - stimuli_amount: 9 -}; - -// Use custom stimuli type with predefined custom_stimulus -const customTypeConfig = { - stimuli_type: 'custom', - stimuli_amount: 7 -}; - -// Enable TTS with custom voice settings -const ttsConfig = { - enable_tts: true, - speak_instructions: true, - speak_prompts: true, - speak_feedback: true, - tts_rate: 0.8, - tts_pitch: 1.1, - tts_lang: 'en-US' -}; -``` - -### Timeline Structure - -The complete timeline includes: - -1. **Instructions** (if `show_instructions: true`) - - Multi-page instructions explaining the task - - Examples of congruent and incongruent trials - - Strategy tips for performance - -2. **Practice Trials** (if `show_practice: true`) - - Highlighted center stimulus to aid learning - - Immediate feedback after each response - - Performance summary at completion - -3. **Main Task** - - Equal numbers of congruent and incongruent trials - - Left and right direction trials - - Randomized trial order - -4. **Completion Screen** - - Final performance summary - - Task completion confirmation - -## Data Generated - -Each trial generates the following data: - -Name | Type | Value ------|------|------ -task | string | 'flanker' for stimulus trials, 'fixation' for fixation periods -phase | string | 'practice' or 'main' depending on trial phase -direction | string | 'left' or 'right' - direction of center stimulus -congruent | boolean | true if flankers match center direction, false if opposite -response | number | 0 for left button, 1 for right button -rt | number | Response time in milliseconds -correct | boolean | true if response matches stimulus direction -trial_number | number | Sequential trial number within phase - -### Performance Calculation - -The `calculatePerformance()` utility function returns: - -```js -{ - accuracy: 85.5, // Percentage of correct responses - mean_rt: 650, // Mean response time for correct trials (ms) - total_trials: 20, // Total number of trials analyzed - correct_trials: 17 // Number of correct responses -} -``` - -## Functions - -### createTimeline(jsPsych, config) - -Creates the complete flanker task timeline with the specified configuration. - -**Parameters:** -- `jsPsych`: jsPsych instance -- `config`: Configuration object (see Parameters section) - -**Returns:** Timeline object ready for use with `jsPsych.run()` - -### calculatePerformance(data) - -Analyzes trial data to compute performance metrics. - -**Parameters:** -- `data`: Array of trial data objects - -**Returns:** Performance object with accuracy, mean RT, and trial counts - -### createFlankerStim(direction, congruent, stimuli, stimuli_amount) - -Generates HTML for flanker stimulus display. - -**Parameters:** -- `direction`: 'left' or 'right' -- `congruent`: boolean indicating trial type -- `stimuli`: Stimuli object or array -- `stimuli_amount`: Number of stimuli to display (default: 5) - -**Returns:** HTML string for stimulus display - -### createPracticeStim(direction, congruent, stimuli, stimuli_amount) - -Generates HTML for practice stimulus with center highlighting. - -**Parameters:** Same as `createFlankerStim()` - -**Returns:** HTML string with highlighted center stimulus - -## Stimulus System - -### Layered SVGs - -The package supports layering multiple SVG elements to create complex stimuli: - -```js -const layeredStimuli = [ - '...base fish shape...', - '...arrow overlay...', - '...additional elements...' -]; -``` - -### Automatic Flipping - -Single SVGs are automatically flipped to create left/right facing versions: - -```js -// Provide only right-facing SVG -const config = { - svg: ['...right-facing fish...'] -}; -// Left-facing version is automatically generated -``` - -### Mobile Optimization - -- Touch-friendly button interfaces -- Responsive sizing using vmin units -- Optimized for various screen sizes -- No keyboard input required - -### Text-to-Speech (TTS) Support - -The package includes comprehensive TTS functionality for accessibility: - -#### Features -- **Configurable voice settings**: Rate, pitch, volume, and language -- **Selective content**: Choose what content to speak (instructions, prompts, feedback) -- **Speech controls**: Space bar to skip current speech -- **Auto-play options**: Automatically start speaking when content loads -- **Browser compatibility**: Uses Web Speech API (available in most modern browsers) - -#### TTS Configuration -```js -const config = { - enable_tts: true, - tts_voice: 'Google US English', // Specific voice name (optional) - tts_rate: 0.9, // Slightly slower than normal - tts_pitch: 1.0, // Normal pitch - tts_volume: 0.8, // 80% volume - tts_lang: 'en-US', // Language code - speak_instructions: true, - speak_prompts: true, - speak_feedback: true, - auto_speak: true -}; -``` - -#### Speech Controls -- **Space bar**: Skip current speech -- **Automatic cleanup**: Speech stops when task completes -- **Error handling**: Graceful fallback if TTS not supported - -#### Browser Support -TTS requires the Web Speech API, supported in: -- Chrome/Chromium browsers -- Safari (desktop and mobile) -- Edge -- Firefox (limited support) - -If TTS is not supported, the task continues normally without speech. - diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html deleted file mode 100644 index 980fbd15..00000000 --- a/packages/flanker-inhibitory/examples/index.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - - Flanker Inhibitory Control Task Example - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/flanker-inhibitory/jest.config.cjs b/packages/flanker-inhibitory/jest.config.cjs deleted file mode 100644 index bd1aacef..00000000 --- a/packages/flanker-inhibitory/jest.config.cjs +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/flanker-inhibitory/package.json b/packages/flanker-inhibitory/package.json deleted file mode 100644 index b032ee44..00000000 --- a/packages/flanker-inhibitory/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@jspsych-timelines/flanker-inhibitory", - "version": "0.0.1", - "description": "Flanker Inhibitory Control and Attention test timeline for jsPsych with configurable fish/arrow stimuli and layered SVG support", - "type": "module", - "main": "dist/index.js", - "types": "dist/index.d.ts", - "unpkg": "dist/index.global.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineFlankerInhibitory" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/jspsych/jspsych-timelines.git", - "directory": "packages/flanker-inhibitory" - }, - "keywords": [ - "jsPsych", - "flanker", - "inhibitory control", - "attention", - "NIH Toolbox", - "cognitive assessment" - ], - "author": { - "name": "Hunter Farhat", - "url": "https://github.com/farhat60" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/jspsych/jspsych-timelines/issues" - }, - "homepage": "https://github.com/jspsych/jspsych-timelines/tree/main/packages/flanker-inhibitory#readme", - "devDependencies": { - "jspsych": "^8.0.0", - "@jspsych/config": "^3.2.2", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - } - } \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts deleted file mode 100644 index 1e158a38..00000000 --- a/packages/flanker-inhibitory/src/index.spec.ts +++ /dev/null @@ -1,750 +0,0 @@ -import { initJsPsych } from "jspsych"; -import { - createTimeline, - createFlankerStim, - createPracticeStim, - calculatePerformance, - default_stimuli, - fish_stimuli, - arrow_stimuli, - timelineUnits, - utils, - trial_text, - instruction_pages, - FlankerConfig -} from "./index"; -import { layered_stimuli } from "./stimuli"; - -describe("Flanker Inhibitory Control Task", () => { - let jsPsych: any; - - beforeEach(() => { - jsPsych = initJsPsych(); - }); - - // Helper function to count only top-level spans (flanker positions) - const countFlankerPositions = (html: string): number => { - // Extract the content inside the flanker-stim div - const flankerMatch = html.match(/
]*>([\s\S]*?)<\/div>/); - if (!flankerMatch) return 0; - - const content = flankerMatch[1]; - - // Count only direct child spans - these are the stimulus positions - // Look for spans that are not nested inside other spans - let count = 0; - let depth = 0; - let i = 0; - - while (i < content.length) { - if (content.substring(i).startsWith('', i) + 1; - } else if (content.substring(i).startsWith('')) { - depth--; - i = content.indexOf('>', i) + 1; - } else { - i++; - } - } - - return count; - }; - - describe("createTimeline", () => { - it("should return a timeline object with timeline array", () => { - const timeline = createTimeline(jsPsych); - expect(timeline).toBeDefined(); - expect(timeline.timeline).toBeDefined(); - expect(Array.isArray(timeline.timeline)).toBe(true); - }); - - it("should create timeline with default parameters", () => { - const timeline = createTimeline(jsPsych); - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - - it("should create timeline without instructions when disabled", () => { - const timeline = createTimeline(jsPsych, { show_instructions: false }); - expect(timeline.timeline.length).toBeGreaterThan(0); - // Should have fewer trials than with instructions - const timelineWithInstructions = createTimeline(jsPsych, { show_instructions: true }); - expect(timeline.timeline.length).toBeLessThan(timelineWithInstructions.timeline.length); - }); - - it("should create timeline without practice when disabled", () => { - const timeline = createTimeline(jsPsych, { show_practice: false }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - - it("should respect custom trial count", () => { - const customTrials = 15; - const timeline = createTimeline(jsPsych, { - num_trials: customTrials, - show_instructions: false, - show_practice: false - }); - - // Should have main trials plus completion screen - expect(timeline.timeline.length).toBeGreaterThanOrEqual(2); - }); - - it("should respect custom practice trial count", () => { - const customPractice = 6; - const timeline = createTimeline(jsPsych, { - num_practice: customPractice, - show_practice: true, - show_instructions: false - }); - - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - - it("should handle different stimuli types", () => { - const fishTimeline = createTimeline(jsPsych, { stimuli_type: 'fish' }); - const arrowTimeline = createTimeline(jsPsych, { stimuli_type: 'arrow' }); - const layeredTimeline = createTimeline(jsPsych, { stimuli_type: 'layered' }); - const customTimeline = createTimeline(jsPsych, { stimuli_type: 'custom' }); - - expect(fishTimeline.timeline.length).toBeGreaterThan(0); - expect(arrowTimeline.timeline.length).toBeGreaterThan(0); - expect(layeredTimeline.timeline.length).toBeGreaterThan(0); - expect(customTimeline.timeline.length).toBeGreaterThan(0); - }); - - it("should handle SVG override parameter", () => { - const customSVGs = [ - '', - '' - ]; - - const timeline = createTimeline(jsPsych, { svg: customSVGs }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - - - it("should set custom fixation duration", () => { - const customDuration = 1000; - const timeline = createTimeline(jsPsych, { fixation_duration: customDuration }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - }); - - describe("createFlankerStim", () => { - it("should create HTML for congruent left stimulus", () => { - const html = createFlankerStim('left', true); - expect(html).toContain('flanker-stim'); - expect(html).toContain(''); - }); - - it("should create HTML for incongruent right stimulus", () => { - const html = createFlankerStim('right', false); - expect(html).toContain('flanker-stim'); - expect(html).toContain(''); - }); - - it("should create HTML for congruent right stimulus", () => { - const html = createFlankerStim('right', true); - expect(html).toContain('flanker-stim'); - expect(html).toContain(''); - }); - - it("should create HTML for incongruent left stimulus", () => { - const html = createFlankerStim('left', false); - expect(html).toContain('flanker-stim'); - expect(html).toContain(''); - }); - - it("should handle custom stimuli arrays", () => { - const customStimuli = ['test']; - const html = createFlankerStim('left', true, customStimuli); - expect(html).toContain('flanker-stim'); - }); - - it("should handle custom stimuli objects", () => { - const customStimuli = { - left: ['left test'], - right: ['right test'] - }; - const html = createFlankerStim('left', true, customStimuli); - expect(html).toContain('flanker-stim'); - }); - }); - - describe("createPracticeStim", () => { - it("should create HTML for practice stimulus with highlighting", () => { - const html = createPracticeStim('left', true); - expect(html).toContain('flanker-stim practice'); - expect(html).toContain('center highlighted'); - }); - - it("should create different HTML than regular stimulus", () => { - const practiceHtml = createPracticeStim('left', true); - const regularHtml = createFlankerStim('left', true); - - expect(practiceHtml).toContain('practice'); - expect(practiceHtml).toContain('highlighted'); - expect(regularHtml).not.toContain('practice'); - expect(regularHtml).not.toContain('highlighted'); - }); - - it("should handle all direction and congruency combinations", () => { - const combinations = [ - ['left', true], - ['left', false], - ['right', true], - ['right', false] - ]; - - combinations.forEach(([direction, congruent]) => { - const html = createPracticeStim(direction as string, congruent as boolean); - expect(html).toContain('flanker-stim practice'); - expect(html).toContain('center highlighted'); - }); - }); - }); - - describe("calculatePerformance", () => { - it("should return zero performance for empty data", () => { - const performance = calculatePerformance([]); - expect(performance.accuracy).toBe(0); - expect(performance.mean_rt).toBe(0); - expect(performance.total_trials).toBe(0); - }); - - it("should calculate accuracy correctly", () => { - const mockData = [ - { task: 'flanker', correct: true, rt: 500 }, - { task: 'flanker', correct: false, rt: 600 }, - { task: 'flanker', correct: true, rt: 550 }, - { task: 'flanker', correct: true, rt: 450 } - ]; - - const performance = calculatePerformance(mockData); - expect(performance.accuracy).toBe(75); // 3/4 = 75% - expect(performance.total_trials).toBe(4); - expect(performance.correct_trials).toBe(3); - expect(performance.mean_rt).toBeCloseTo(500, 1); // Mean of correct trials: (500+550+450)/3 - }); - - it("should filter only flanker task data", () => { - const mockData = [ - { task: 'flanker', correct: true, rt: 500 }, - { task: 'other', correct: false, rt: 600 }, - { task: 'flanker', correct: true, rt: 550 } - ]; - - const performance = calculatePerformance(mockData); - expect(performance.total_trials).toBe(2); // Only flanker trials - expect(performance.accuracy).toBe(100); // Both flanker trials correct - }); - - it("should handle all incorrect trials", () => { - const mockData = [ - { task: 'flanker', correct: false, rt: 500 }, - { task: 'flanker', correct: false, rt: 600 } - ]; - - const performance = calculatePerformance(mockData); - expect(performance.accuracy).toBe(0); - expect(performance.mean_rt).toBe(0); // No correct trials - expect(performance.correct_trials).toBe(0); - }); - }); - - describe("Stimulus Constants", () => { - it("should provide default_stimuli with left and right", () => { - expect(default_stimuli).toBeDefined(); - expect(default_stimuli.left).toBeDefined(); - expect(default_stimuli.right).toBeDefined(); - expect(typeof default_stimuli.left).toBe('string'); - expect(typeof default_stimuli.right).toBe('string'); - }); - - it("should provide fish_stimuli with left and right", () => { - expect(fish_stimuli).toBeDefined(); - expect(fish_stimuli.left).toBeDefined(); - expect(fish_stimuli.right).toBeDefined(); - expect(typeof fish_stimuli.left).toBe('string'); - expect(typeof fish_stimuli.right).toBe('string'); - }); - - it("should provide arrow_stimuli with left and right", () => { - expect(arrow_stimuli).toBeDefined(); - expect(arrow_stimuli.left).toBeDefined(); - expect(arrow_stimuli.right).toBeDefined(); - expect(typeof arrow_stimuli.left).toBe('string'); - expect(typeof arrow_stimuli.right).toBe('string'); - }); - - it("should have different stimuli for left and right directions", () => { - expect(default_stimuli.left).not.toBe(default_stimuli.right); - expect(fish_stimuli.left).not.toBe(fish_stimuli.right); - expect(arrow_stimuli.left).not.toBe(arrow_stimuli.right); - }); - }); - - describe("Text and Instructions", () => { - it("should provide trial_text object", () => { - expect(trial_text).toBeDefined(); - expect(trial_text.continue_button).toBeDefined(); - expect(trial_text.left_button).toBeDefined(); - expect(trial_text.right_button).toBeDefined(); - expect(trial_text.fixation_cross).toBeDefined(); - expect(trial_text.correct_feedback).toBeDefined(); - expect(trial_text.incorrect_feedback).toBeDefined(); - }); - - it("should provide instruction_pages array", () => { - expect(instruction_pages).toBeDefined(); - expect(Array.isArray(instruction_pages)).toBe(true); - expect(instruction_pages.length).toBeGreaterThan(0); - }); - - it("should have properly structured instruction pages", () => { - instruction_pages.forEach((page, index) => { - expect(page).toBeDefined(); - expect(page.buttons).toBeDefined(); - expect(Array.isArray(page.buttons)).toBe(true); - }); - }); - }); - - describe("Timeline Units", () => { - it("should provide timelineUnits object", () => { - expect(timelineUnits).toBeDefined(); - expect(timelineUnits.instructions).toBeDefined(); - expect(timelineUnits.practice).toBeDefined(); - expect(timelineUnits.main).toBeDefined(); - expect(timelineUnits.completion).toBeDefined(); - }); - }); - - describe("Utils Export", () => { - it("should provide utils object with functions", () => { - expect(utils).toBeDefined(); - expect(utils.calculatePerformance).toBeDefined(); - expect(utils.createFlankerStim).toBeDefined(); - expect(utils.createPracticeStim).toBeDefined(); - expect(typeof utils.calculatePerformance).toBe('function'); - expect(typeof utils.createFlankerStim).toBe('function'); - expect(typeof utils.createPracticeStim).toBe('function'); - }); - - it("should have utils functions work the same as direct exports", () => { - const mockData = [{ task: 'flanker', correct: true, rt: 500 }]; - - const directResult = calculatePerformance(mockData); - const utilsResult = utils.calculatePerformance(mockData); - - expect(directResult).toEqual(utilsResult); - }); - }); - - describe("FlankerConfig Interface", () => { - it("should accept valid config objects", () => { - const configs: FlankerConfig[] = [ - {}, - { stimuli_type: 'fish' }, - { stimuli_type: 'arrow' }, - { stimuli_type: 'layered' }, - { fixation_duration: 1000 }, - { show_instructions: false }, - { show_practice: true }, - { num_practice: 10 }, - { num_trials: 25 }, - { svg: ['test'] } - ]; - - configs.forEach(config => { - expect(() => createTimeline(jsPsych, config)).not.toThrow(); - }); - }); - }); - - describe("SVG Flipping Functionality", () => { - it("should generate different left and right stimuli from same input", () => { - // Test with a simple SVG array input - const testSVG = ['']; - - const leftHtml = createFlankerStim('left', true, testSVG); - const rightHtml = createFlankerStim('right', true, testSVG); - - // Should be different due to flipping - expect(leftHtml).not.toBe(rightHtml); - expect(leftHtml).toContain('flanker-stim'); - expect(rightHtml).toContain('flanker-stim'); - }); - - it("should handle SVGs with existing transforms", () => { - // Test with SVG that already has transform (like the fish) - const transformedSVG = ['']; - - const leftHtml = createFlankerStim('left', true, transformedSVG); - const rightHtml = createFlankerStim('right', true, transformedSVG); - - expect(leftHtml).not.toBe(rightHtml); - expect(leftHtml).toContain('flanker-stim'); - expect(rightHtml).toContain('flanker-stim'); - }); - - it("should properly handle fish SVG alignment - no double transforms", () => { - // Test specifically with fish stimuli to ensure alignment fix works - const fishLeftHtml = createFlankerStim('left', true, fish_stimuli); - const fishRightHtml = createFlankerStim('right', true, fish_stimuli); - - // Both should contain flanker stimulus - expect(fishLeftHtml).toContain('flanker-stim'); - expect(fishRightHtml).toContain('flanker-stim'); - - // They should be different (one flipped) - expect(fishLeftHtml).not.toBe(fishRightHtml); - - // Test that the fish SVG original has a transform - expect(fish_stimuli.right).toContain('transform='); - - // Test that flipping twice gets back to original orientation - // This validates the fix for double-transform issues - const twice_flipped_left = createFlankerStim('left', true, fish_stimuli); - const twice_flipped_right = createFlankerStim('right', true, fish_stimuli); - expect(twice_flipped_left).not.toBe(twice_flipped_right); - }); - - it("should handle fish incongruent stimuli alignment correctly", () => { - // Test incongruent fish (center different from flankers) - key alignment test - const leftIncongruent = createFlankerStim('left', false, fish_stimuli); - const rightIncongruent = createFlankerStim('right', false, fish_stimuli); - - // Should produce valid HTML - expect(leftIncongruent).toContain('flanker-stim'); - expect(rightIncongruent).toContain('flanker-stim'); - - // Should be different - expect(leftIncongruent).not.toBe(rightIncongruent); - - // The key test: each should contain both orientations (center vs flankers) - // This is where alignment issues would be most visible - expect(leftIncongruent).toContain(''); - expect(rightIncongruent).toContain(''); - }); - - it("should maintain consistent alignment with layered fish+arrow stimuli", () => { - // Test the default layered stimuli (fish + arrow) for alignment - const leftLayered = createFlankerStim('left', true, default_stimuli); - const rightLayered = createFlankerStim('right', true, default_stimuli); - - expect(leftLayered).toContain('flanker-stim'); - expect(rightLayered).toContain('flanker-stim'); - expect(leftLayered).not.toBe(rightLayered); - - // Should contain position styling for layered elements - expect(leftLayered).toContain('position:'); - expect(rightLayered).toContain('position:'); - }); - }); - - describe("Layered SVG Functionality", () => { - it("should handle multiple SVG layers", () => { - const layeredSVGs = [ - '', - '' - ]; - - const html = createFlankerStim('left', true, layeredSVGs); - - expect(html).toContain('flanker-stim'); - expect(html).toContain('position: relative'); - }); - - it("should handle single SVG in array", () => { - const singleSVG = ['']; - - const html = createFlankerStim('left', true, singleSVG); - - expect(html).toContain('flanker-stim'); - }); - }); - - describe("Stimuli Amount Functionality", () => { - - it("should default to 5 stimuli (2 left + 1 center + 2 right)", () => { - const html = createFlankerStim('left', true); - const spanCount = countFlankerPositions(html); - expect(spanCount).toBe(5); - }); - - it("should create correct number of stimuli for custom amounts", () => { - const testAmounts = [3, 5, 7, 9]; - - testAmounts.forEach(amount => { - const html = createFlankerStim('left', true, layered_stimuli, amount); - const spanCount = countFlankerPositions(html); - expect(spanCount).toBe(amount); - }); - }); - - it("should handle even numbers by rounding up to next odd", () => { - const html4 = createFlankerStim('left', true, layered_stimuli, 4); - const html6 = createFlankerStim('left', true, layered_stimuli, 6); - const html8 = createFlankerStim('left', true, layered_stimuli, 8); - - expect(countFlankerPositions(html4)).toBe(5); // 4 -> 5 - expect(countFlankerPositions(html6)).toBe(7); // 6 -> 7 - expect(countFlankerPositions(html8)).toBe(9); // 8 -> 9 - }); - - it("should enforce minimum of 3 stimuli", () => { - const html1 = createFlankerStim('left', true, layered_stimuli, 1); - const html2 = createFlankerStim('left', true, layered_stimuli, 2); - const htmlNegative = createFlankerStim('left', true, layered_stimuli, -5); - - expect(countFlankerPositions(html1)).toBe(3); - expect(countFlankerPositions(html2)).toBe(3); - expect(countFlankerPositions(htmlNegative)).toBe(3); - }); - - it("should maintain center stimulus with highlighting in practice", () => { - const html = createPracticeStim('left', true, layered_stimuli, 7); - const spanCount = countFlankerPositions(html); - expect(spanCount).toBe(7); - expect(html).toContain('center highlighted'); - }); - - it("should work with different stimuli types", () => { - const fishHtml = createFlankerStim('left', true, fish_stimuli, 7); - const arrowHtml = createFlankerStim('left', true, arrow_stimuli, 7); - - expect(countFlankerPositions(fishHtml)).toBe(7); - expect(countFlankerPositions(arrowHtml)).toBe(7); - }); - - it("should be configurable via timeline config", () => { - const timeline = createTimeline(jsPsych, { - stimuli_amount: 7, - show_instructions: false, - show_practice: false - }); - - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - - it("should validate stimuli_amount in different function calls", () => { - // Test with simple SVGs (no inner spans) - const testSVGs = ['']; - const html1 = createFlankerStim('left', true, testSVGs, 9); - expect(countFlankerPositions(html1)).toBe(9); - - // Test with objects - const testObj = { left: [''], right: [''] }; - const html2 = createFlankerStim('left', true, testObj, 7); - expect(countFlankerPositions(html2)).toBe(7); - }); - - it("should handle invalid amounts by using valid fallbacks", () => { - // NaN should fall back to default (5) - const htmlNaN = createFlankerStim('left', true, layered_stimuli, NaN); - expect(countFlankerPositions(htmlNaN)).toBe(5); - - // Very large numbers should still work - const htmlLarge = createFlankerStim('left', true, layered_stimuli, 15); - expect(countFlankerPositions(htmlLarge)).toBe(15); - }); - }); - - describe("Error Handling", () => { - it("should handle invalid direction gracefully", () => { - expect(() => createFlankerStim('invalid' as any, true)).not.toThrow(); - }); - - it("should handle undefined stimuli", () => { - expect(() => createFlankerStim('left', true, undefined as any)).not.toThrow(); - }); - - it("should handle empty stimuli array", () => { - expect(() => createFlankerStim('left', true, [])).not.toThrow(); - }); - - it("should handle malformed SVG strings", () => { - const badSVG = ['not an svg']; - expect(() => createFlankerStim('left', true, badSVG)).not.toThrow(); - }); - - it("should handle invalid stimuli_amount gracefully", () => { - expect(() => createFlankerStim('left', true, layered_stimuli, NaN)).not.toThrow(); - expect(() => createFlankerStim('left', true, layered_stimuli, Infinity)).not.toThrow(); - - // Should fall back to default value (5) for invalid inputs - const htmlNaN = createFlankerStim('left', true, layered_stimuli, NaN); - const htmlInfinity = createFlankerStim('left', true, layered_stimuli, Infinity); - - expect(countFlankerPositions(htmlNaN)).toBe(5); - expect(countFlankerPositions(htmlInfinity)).toBe(5); - }); - }); - - describe("Edge Cases and Timeline Variable Errors", () => { - it("should handle num_practice=0 with show_practice=true without errors", () => { - expect(() => { - const timeline = createTimeline(jsPsych, { - num_practice: 0, - show_practice: true, - show_instructions: false, - num_trials: 1 - }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - - it("should handle num_trials=0 without errors", () => { - expect(() => { - const timeline = createTimeline(jsPsych, { - num_trials: 0, - show_instructions: false, - show_practice: false - }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - - it("should handle both num_practice=0 and num_trials=0 without errors", () => { - expect(() => { - const timeline = createTimeline(jsPsych, { - num_practice: 0, - show_practice: true, - num_trials: 0, - show_instructions: false - }); - // Should still have completion screen - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - - it("should handle negative numbers for practice trials", () => { - expect(() => { - const timeline = createTimeline(jsPsych, { - num_practice: -5, - show_practice: true, - show_instructions: false, - num_trials: 1 - }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - - it("should handle negative numbers for main trials", () => { - expect(() => { - const timeline = createTimeline(jsPsych, { - num_trials: -10, - show_instructions: false, - show_practice: false - }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - - it("should handle very large numbers without breaking", () => { - expect(() => { - const timeline = createTimeline(jsPsych, { - num_practice: 1000, - show_practice: true, - num_trials: 1000, - show_instructions: false - }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - - it("should handle NaN values for trial counts", () => { - expect(() => { - const timeline = createTimeline(jsPsych, { - num_practice: NaN, - show_practice: true, - num_trials: NaN, - show_instructions: false - }); - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - - it("should handle all combinations of false/0 settings", () => { - const edgeCaseConfigs = [ - { show_instructions: false, show_practice: false, num_practice: 0, num_trials: 0 }, - { show_instructions: true, show_practice: false, num_practice: 0, num_trials: 0 }, - { show_instructions: false, show_practice: true, num_practice: 0, num_trials: 0 }, - { show_instructions: false, show_practice: false, num_practice: 5, num_trials: 0 }, - { show_instructions: false, show_practice: false, num_practice: 0, num_trials: 5 }, - ]; - - edgeCaseConfigs.forEach((config, index) => { - expect(() => { - const timeline = createTimeline(jsPsych, config); - expect(timeline.timeline.length).toBeGreaterThan(0); - }).not.toThrow(); - }); - }); - - it("should always include completion screen even with no trials", () => { - const timeline = createTimeline(jsPsych, { - show_instructions: false, - show_practice: false, - num_practice: 0, - num_trials: 0 - }); - - // Should have at least the main intro and completion screens - expect(timeline.timeline.length).toBeGreaterThanOrEqual(2); - - // Check that completion screen is present - const completionScreen = timeline.timeline.find(trial => - trial.data && trial.data.task === 'complete' - ); - expect(completionScreen).toBeDefined(); - }); - }); - - describe("Responsive Sizing", () => { - it("should add CSS custom property for stimuli count", () => { - const html3 = createFlankerStim('left', true, fish_stimuli, 3); - const html7 = createFlankerStim('left', true, fish_stimuli, 7); - const html15 = createFlankerStim('left', true, fish_stimuli, 15); - - expect(html3).toContain('--stimuli-count: 3'); - expect(html7).toContain('--stimuli-count: 7'); - expect(html15).toContain('--stimuli-count: 15'); - }); - - it("should add CSS custom property to practice stimuli", () => { - const practiceHtml = createPracticeStim('right', false, arrow_stimuli, 9); - - expect(practiceHtml).toContain('--stimuli-count: 9'); - expect(practiceHtml).toContain('practice'); - }); - - it("should handle different stimuli counts with custom property", () => { - const testCounts = [3, 5, 7, 9, 11, 15, 21]; - - testCounts.forEach(count => { - const html = createFlankerStim('left', true, default_stimuli, count); - expect(html).toContain(`--stimuli-count: ${count}`); - expect(html).toContain('flanker-stim'); - }); - }); - - it("should work with edge cases for responsive sizing", () => { - // Test with very large count - const htmlLarge = createFlankerStim('left', true, fish_stimuli, 25); - expect(htmlLarge).toContain('--stimuli-count: 25'); - - // Test with minimum count (enforced to 3) - const htmlMin = createFlankerStim('left', true, fish_stimuli, 1); - expect(htmlMin).toContain('--stimuli-count: 3'); - - // Test with invalid input (should fall back to default) - const htmlInvalid = createFlankerStim('left', true, fish_stimuli, NaN); - expect(htmlInvalid).toContain('--stimuli-count: 5'); - }); - }); -}); \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts deleted file mode 100644 index 91185ff0..00000000 --- a/packages/flanker-inhibitory/src/index.ts +++ /dev/null @@ -1,768 +0,0 @@ -import { JsPsych } from "jspsych"; -import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; -import { trial_text, instruction_pages, tts_config, tts_text } from './text'; -import { layered_stimuli, fish_only, arrow_only, custom_stimulus } from './stimuli'; - -// ============================================================================ -// TTS (TEXT-TO-SPEECH) UTILITIES -// ============================================================================ - -// Global TTS state management -let currentUtterance: SpeechSynthesisUtterance | null = null; -let speechQueue: string[] = []; -let speechEnabled = false; - -// Initialize TTS functionality -export function initializeTTS(config = tts_config) { - speechEnabled = 'speechSynthesis' in window; - - if (!speechEnabled) { - console.warn('Text-to-Speech not supported in this browser'); - return false; - } - - // Stop any existing speech - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } - - return true; -} - -// Get available voices -export function getAvailableVoices(): SpeechSynthesisVoice[] { - return speechSynthesis.getVoices(); -} - -// Create speech utterance with configuration -export function createUtterance(text: string, config = tts_config): SpeechSynthesisUtterance { - const utterance = new SpeechSynthesisUtterance(text); - - // Apply voice settings - utterance.rate = config.rate; - utterance.pitch = config.pitch; - utterance.volume = config.volume; - utterance.lang = config.lang; - - // Set voice if specified - if (config.voice_name) { - const voices = getAvailableVoices(); - const selectedVoice = voices.find(voice => - voice.name.includes(config.voice_name) || voice.lang === config.lang - ); - if (selectedVoice) { - utterance.voice = selectedVoice; - } - } - - return utterance; -} - -// Stop current speech -export function stopSpeech() { - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } - currentUtterance = null; - speechQueue = []; -} - -// Speak text with delay and configuration -export function speakText(text: string, config = tts_config, delay = config.speech_delay): Promise { - return new Promise((resolve) => { - if (!speechEnabled || !text.trim()) { - resolve(); - return; - } - - setTimeout(() => { - // Stop any current speech - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } - - currentUtterance = createUtterance(text, config); - - currentUtterance.onend = () => { - currentUtterance = null; - resolve(); - }; - - currentUtterance.onerror = () => { - currentUtterance = null; - resolve(); - }; - - speechSynthesis.speak(currentUtterance); - }, delay); - }); -} - -// Speak instruction page content -export function speakInstructionPage(pageContent: string, config = tts_config): Promise { - if (!config.speak_instructions) { - return Promise.resolve(); - } - - // Remove HTML tags and clean up text for speech - const cleanText = pageContent - .replace(/<[^>]*>/g, ' ') // Remove HTML tags - .replace(/\s+/g, ' ') // Normalize any sequence of whitespace (including those left by the previous replace) to a single space - .trim(); - - return speakText(cleanText, config); -} - -// Speak trial prompt -export function speakTrialPrompt(config = tts_config): Promise { - if (!config.speak_prompts) { - return Promise.resolve(); - } - - return speakText(tts_text.trial_prompt_spoken, config); -} - -// Speak feedback -export function speakFeedback(isCorrect: boolean, config = tts_config): Promise { - if (!config.speak_feedback) { - return Promise.resolve(); - } - - const feedbackText = isCorrect ? tts_text.correct_spoken : tts_text.incorrect_spoken; - return speakText(feedbackText, config); -} - -// Add keyboard listener for speech control -export function addSpeechControls(config = tts_config) { - if (!config.allow_skip) return undefined; - - const handleKeyPress = (event: KeyboardEvent) => { - if (event.code === 'Space' && speechSynthesis.speaking) { - event.preventDefault(); - stopSpeech(); - } - }; - - document.addEventListener('keydown', handleKeyPress); - - // Return cleanup function - return () => { - document.removeEventListener('keydown', handleKeyPress); - }; -} - -// ============================================================================ -// SVG UTILITIES -// ============================================================================ - -// Robust SVG flipping with proper alignment handling -function flipSVG(svgString: string): string { - if (!svgString || typeof svgString !== 'string') return svgString; - - // Check if SVG already contains a flip transform - const hasExistingTransform = svgString.includes('matrix(-1,') || - svgString.includes('matrix(-1 ') || - svgString.includes('scaleX(-1)') || - svgString.includes('scale(-1'); - - // If already flipped, remove the flip transform to restore original orientation - if (hasExistingTransform) { - return svgString - .replace(/transform="matrix\(-1,\s*0,\s*0,\s*1,\s*0,\s*0\)"/g, '') - .replace(/transform="matrix\(-1\s+0\s+0\s+1\s+0\s+0\)"/g, '') - .replace(/transform="scaleX\(-1\)"/g, '') - .replace(/transform="scale\(-1,\s*1\)"/g, '') - .replace(/transform="scale\(-1\s+1\)"/g, '') - .replace(/style="[^"]*transform:\s*scaleX\(-1\)[^"]*"/g, (match) => { - return match.replace(/transform:\s*scaleX\(-1\);?/, '').replace(/style=""/, ''); - }); - } - - // Parse SVG to get dimensions for proper transform calculation - const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/); - const widthMatch = svgString.match(/width="([^"]+)"/); - - let translateX = 0; - if (viewBoxMatch) { - const viewBox = viewBoxMatch[1].split(/\s+/); - if (viewBox.length >= 3) { - translateX = parseFloat(viewBox[2]) || 0; // width from viewBox - } - } else if (widthMatch) { - translateX = parseFloat(widthMatch[1]) || 0; - } - - // Apply proper SVG transform that maintains alignment - if (translateX > 0) { - // Insert transform into the SVG element itself for better precision - return svgString.replace( - /]*)>/, - `` - ).replace(/<\/svg>$/, ''); - } else { - // Fallback to CSS transform with inline-block container - return `${svgString}`; - } -} - -// Simple SVG layering using CSS -function layerSVGs(svgArray: string[], flip: boolean = false): string { - if (!svgArray || !Array.isArray(svgArray) || svgArray.length === 0) return ''; - if (svgArray.length === 1) return flip ? flipSVG(svgArray[0]) : svgArray[0]; - - // Layer SVGs using CSS positioning, flip each SVG individually if needed - return ` - ${svgArray.map((svg, index) => - `${flip ? flipSVG(svg) : svg}` - ).join('')} - `; -} - -// Simplified stimulus processing -function processStimuli(stimuli: { left?: string[], right?: string[] } | string[] | { left: string, right: string }) { - // If already processed (has left/right strings), return as-is - if (stimuli && typeof stimuli === 'object' && !Array.isArray(stimuli) && - typeof stimuli.left === 'string' && typeof stimuli.right === 'string') { - return stimuli as { left: string, right: string }; - } - - // If simple array, treat as right-facing and generate left by flipping - if (Array.isArray(stimuli)) { - return { - right: layerSVGs(stimuli, false), - left: layerSVGs(stimuli, true) - }; - } - - // If object with arrays - if (stimuli && typeof stimuli === 'object') { - const obj = stimuli as { left?: string[], right?: string[] }; - if (obj.left && obj.right) { - return { - left: layerSVGs(obj.left, false), - right: layerSVGs(obj.right, false) - }; - } else if (obj.right) { - return { - right: layerSVGs(obj.right, false), - left: layerSVGs(obj.right, true) - }; - } else if (obj.left) { - return { - left: layerSVGs(obj.left, false), - right: layerSVGs(obj.left, true) - }; - } - } - - // Default fallback - return { - right: layerSVGs(layered_stimuli, false), - left: layerSVGs(layered_stimuli, true) - }; -} - -// Pre-processed stimuli for convenience (lazy-loaded) -export const default_stimuli = processStimuli(layered_stimuli); -export const fish_stimuli = processStimuli(fish_only); -export const arrow_stimuli = processStimuli(arrow_only); - -// ============================================================================ -// STIMULUS CREATION FUNCTIONS -// ============================================================================ - -export function createFlankerStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { - // Ensure stimuli_amount is valid (odd number ≥3) - let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; - const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); - const halfFlankers = Math.floor(validAmount / 2); - - // Process stimuli - const processedStimuli = processStimuli(stimuli); - const center = processedStimuli[direction]; - const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; - - let html = `
`; - - // Add left flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - // Add center stimulus - html += `${center}`; - - // Add right flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - html += `
`; - return html; -} - -export function createPracticeStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { - // Ensure stimuli_amount is valid (odd number ≥3) - let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; - const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); - const halfFlankers = Math.floor(validAmount / 2); - - // Process stimuli - const processedStimuli = processStimuli(stimuli); - const center = processedStimuli[direction]; - const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; - - let html = `
`; - - // Add left flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - // Add center stimulus with highlighting - html += `${center}`; - - // Add right flankers - for (let i = 0; i < halfFlankers; i++) { - html += `${flanker}`; - } - - html += `
`; - return html; -} - -// ============================================================================ -// INTERFACES -// ============================================================================ - -export interface FlankerConfig { - stimuli_type?: 'fish' | 'arrow' | 'layered' | 'custom'; - svg?: string[]; // Override parameter - array of SVGs to layer - stimuli_amount?: number; // Number of stimuli in flanker display (default: 5, must be odd ≥3) - fixation_duration?: number; - show_instructions?: boolean; - show_practice?: boolean; - num_practice?: number; - num_trials?: number; - - // TTS Configuration - enable_tts?: boolean; // Enable text-to-speech functionality - tts_voice?: string; // Voice name to use for TTS - tts_rate?: number; // Speech rate (0.1 to 10) - tts_pitch?: number; // Speech pitch (0 to 2) - tts_volume?: number; // Speech volume (0 to 1) - tts_lang?: string; // Language code for TTS - speak_instructions?: boolean; // Speak instruction pages - speak_prompts?: boolean; // Speak trial prompts - speak_feedback?: boolean; // Speak feedback messages - auto_speak?: boolean; // Automatically start speaking when content loads -} - -// ============================================================================ -// UTILITY FUNCTIONS -// ============================================================================ - -export function calculatePerformance(data: any[]) { - const trial_data = data.filter(d => d.task === 'flanker'); - - if (trial_data.length === 0) { - return { accuracy: 0, mean_rt: 0, total_trials: 0 }; - } - - const correct_trials = trial_data.filter(d => d.correct); - const accuracy = (correct_trials.length / trial_data.length) * 100; - const mean_rt = correct_trials.length > 0 - ? correct_trials.reduce((sum, d) => sum + d.rt, 0) / correct_trials.length - : 0; - - return { - accuracy, - mean_rt, - total_trials: trial_data.length, - correct_trials: correct_trials.length - }; -} - -// ============================================================================ -// MAIN TIMELINE FUNCTION -// ============================================================================ - -export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { - const { - stimuli_type = 'layered', - svg, - stimuli_amount = 5, - fixation_duration = 500, - show_instructions = true, - show_practice = true, - num_practice = 8, - num_trials = 20, - - // TTS configuration - enable_tts = false, - tts_voice = tts_config.voice_name, - tts_rate = tts_config.rate, - tts_pitch = tts_config.pitch, - tts_volume = tts_config.volume, - tts_lang = tts_config.lang, - speak_instructions = tts_config.speak_instructions, - speak_prompts = tts_config.speak_prompts, - speak_feedback = tts_config.speak_feedback, - auto_speak = tts_config.auto_speak_on_load, - } = config; - - // Initialize TTS if enabled - let ttsSettings = { ...tts_config }; - let speechCleanup: (() => void) | undefined; - - if (enable_tts) { - ttsSettings = { - ...tts_config, - voice_name: tts_voice, - rate: tts_rate, - pitch: tts_pitch, - volume: tts_volume, - lang: tts_lang, - speak_instructions, - speak_prompts, - speak_feedback, - auto_speak_on_load: auto_speak, - }; - - const ttsInitialized = initializeTTS(ttsSettings); - if (ttsInitialized) { - speechCleanup = addSpeechControls(ttsSettings); - } - } - - // Determine which stimuli to use based on priority: - // 1. svg override parameter (highest priority) - // 2. stimuli_type selection - let stimuli; - if (svg) { - stimuli = processStimuli(svg); // Process the SVG override array to handle flipping - } else if (stimuli_type === 'custom') { - stimuli = processStimuli(custom_stimulus); - } else if (stimuli_type === 'arrow') { - stimuli = arrow_stimuli; - } else if (stimuli_type === 'fish') { - stimuli = fish_stimuli; - } else { - stimuli = default_stimuli; // layered by default - } - const timeline = []; - - // Instructions - if (show_instructions) { - instruction_pages.forEach((page, index) => { - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
- ${page.header ? `

${page.header}

` : ''} - ${page.header2 ? `

${page.header2}

` : ''} - ${page.description ? `

${page.description}

` : ''} - ${page.task_explanation ? `

${page.task_explanation}

` : ''} - ${page.performance_note ? `

${page.performance_note}

` : ''} - ${page.strategy_title ? `

${page.strategy_title}

` : ''} - ${page.strategy_intro ? `

${page.strategy_intro}

` : ''} - ${page.strategy_points ? ` -
    - ${page.strategy_points.map(point => `
  • ${point}
  • `).join('')} -
- ` : ''} - ${page.start_prompt ? `

${page.start_prompt}

` : ''} - ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `, - choices: page.buttons || [trial_text.continue_button], - data: { task: 'instructions', page: index + 1 }, - on_load: enable_tts ? function() { - if (index === 0) { - // Speak welcome message for first page - speakText(tts_text.welcome_spoken, ttsSettings); - } else if (index === 1) { - // Speak task explanation for second page - speakText(tts_text.task_explanation_spoken, ttsSettings); - } else if (index === 2) { - // Speak directions for third page - speakText(tts_text.directions_spoken, ttsSettings); - } - } : undefined - }); - }); - } - - // Practice - if (show_practice) { - // Practice intro - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
-

${trial_text.practice_header}

-

${trial_text.practice_intro_message}

- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `, - choices: [trial_text.start_button], - data: { task: 'practice-intro' }, - on_load: enable_tts ? function() { - speakText(tts_text.practice_start_spoken, ttsSettings); - } : undefined - }); - - // Only add practice trials if num_practice > 0 - if (num_practice > 0) { - // Practice trials - const practice_variables = [ - {direction: 'left', congruent: true}, - {direction: 'left', congruent: false}, - {direction: 'right', congruent: true}, - {direction: 'right', congruent: false}, - ]; - - const practice_trials = jsPsych.randomization.repeat(practice_variables, Math.floor(num_practice/4)) - .concat(jsPsych.randomization.sampleWithoutReplacement(practice_variables, num_practice%4)); - - // Fixation - const practice_fixation = { - type: jsPsychHtmlButtonResponse, - stimulus: `
${trial_text.fixation_cross}
`, - choices: [], - trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'practice' } - }; - - // Practice trial - const practice_trial = { - type: jsPsychHtmlButtonResponse, - stimulus: function() { - const direction = jsPsych.evaluateTimelineVariable('direction'); - const congruent = jsPsych.evaluateTimelineVariable('congruent'); - return ` -
-
${trial_text.main_task_prompt}
- ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} -
- `; - }, - choices: [trial_text.left_button, trial_text.right_button], - data: { - task: 'flanker', - phase: 'practice', - direction: jsPsych.timelineVariable('direction'), - congruent: jsPsych.timelineVariable('congruent') - }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_prompts) { - speakTrialPrompt(ttsSettings); - } - } : undefined, - on_finish: function(data: any) { - const correct_response = data.direction === 'left' ? 0 : 1; - data.correct = data.response === correct_response; - } - }; - - // Feedback - const practice_feedback = { - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const last_trial = jsPsych.data.get().last(1).values()[0]; - const feedback = last_trial.correct ? trial_text.correct_feedback : trial_text.incorrect_feedback; - return ``; - }, - choices: [], - trial_duration: 1000, - data: { task: 'feedback', phase: 'practice' }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_feedback) { - const last_trial = jsPsych.data.get().last(1).values()[0]; - speakFeedback(last_trial.correct, ttsSettings); - } - } : undefined - }; - - // Practice timeline - timeline.push({ - timeline: [practice_fixation, practice_trial, practice_feedback], - timeline_variables: practice_trials, - randomize_order: true - }); - } - - // Practice completion - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); - const performance = calculatePerformance(practice_data); - - return ` -
-

${trial_text.practice_complete_header}

-

${trial_text.practice_complete_message}

-

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `; - }, - choices: [trial_text.continue_button], - data: { task: 'practice-complete' }, - on_load: enable_tts ? function() { - speakText(tts_text.practice_complete_spoken, ttsSettings); - } : undefined - }); - } - - // Main task - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
-

${trial_text.main_task_header}

-

${trial_text.main_task_intro}

- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `, - choices: [trial_text.ready_button], - data: { task: 'main-intro' }, - on_load: enable_tts ? function() { - speakText(tts_text.main_task_start_spoken, ttsSettings); - } : undefined - }); - - // Main trials - only add if num_trials > 0 - if (num_trials > 0) { - const trial_variables = [ - {direction: 'left', congruent: true}, - {direction: 'left', congruent: false}, - {direction: 'right', congruent: true}, - {direction: 'right', congruent: false}, - ]; - - const main_trials = jsPsych.randomization.repeat(trial_variables, Math.floor(num_trials/4)) - .concat(jsPsych.randomization.sampleWithoutReplacement(trial_variables, num_trials%4)); - - // Fixation - const fixation = { - type: jsPsychHtmlButtonResponse, - stimulus: `
${trial_text.fixation_cross}
`, - choices: [], - trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'main' } - }; - - // Main trial - const flanker_trial = { - type: jsPsychHtmlButtonResponse, - stimulus: function() { - const direction = jsPsych.evaluateTimelineVariable('direction'); - const congruent = jsPsych.evaluateTimelineVariable('congruent'); - return ` -
-
${trial_text.main_task_prompt}
- ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)} -
- `; - }, - choices: [trial_text.left_button, trial_text.right_button], - data: { - task: 'flanker', - phase: 'main', - direction: jsPsych.timelineVariable('direction'), - congruent: jsPsych.timelineVariable('congruent') - }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_prompts) { - speakTrialPrompt(ttsSettings); - } - } : undefined, - on_finish: function(data: any) { - const correct_response = data.direction === 'left' ? 0 : 1; - data.correct = data.response === correct_response; - } - }; - - // Main task timeline - timeline.push({ - timeline: [fixation, flanker_trial], - timeline_variables: main_trials, - randomize_order: true - }); - } - - // Completion - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: () => { - const main_data = jsPsych.data.get().filter({task: 'flanker', phase: 'main'}).values(); - const performance = calculatePerformance(main_data); - - return ` -
-

${trial_text.task_complete_header}

-

${trial_text.task_complete_message}

-
-

${trial_text.performance_title}

-

${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

-

${trial_text.response_time_label} ${performance.mean_rt.toFixed(0)}ms

-
- ${enable_tts && ttsSettings.allow_skip ? `

${tts_text.speech_controls_note}

` : ''} -
- `; - }, - choices: [trial_text.end_button], - data: { task: 'complete' }, - on_load: enable_tts ? function() { - if (ttsSettings.speak_completion) { - speakText(tts_text.task_complete_spoken, ttsSettings); - } - } : undefined, - on_finish: function() { - // Cleanup speech controls when task is complete - if (speechCleanup) { - speechCleanup(); - } - stopSpeech(); - } - }); - - return { - timeline: timeline - }; -} - -// ============================================================================ -// EXPORTS -// ============================================================================ - -export const timelineUnits = { - instructions: "Instructions for the flanker inhibitory control task", - practice: "Practice trials with feedback", - main: "Main flanker task trials", - completion: "Task completion screen" -}; - -export const utils = { - calculatePerformance, - createFlankerStim, - createPracticeStim, - - // TTS utilities - initializeTTS, - speakText, - stopSpeech, - getAvailableVoices, - speakInstructionPage, - speakTrialPrompt, - speakFeedback, - addSpeechControls -}; - -// Export text for external use -export { trial_text, instruction_pages, tts_config, tts_text } from './text'; - -// Default export -export default { createTimeline, timelineUnits, utils }; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/stimuli.ts b/packages/flanker-inhibitory/src/stimuli.ts deleted file mode 100644 index fed70288..00000000 --- a/packages/flanker-inhibitory/src/stimuli.ts +++ /dev/null @@ -1,52 +0,0 @@ -// ============================================================================ -// LAYERED SVG STIMULI -// ============================================================================ - -// Fish SVG (base layer) -const fish_svg = ` - - - - - - - - -`; - -// Arrow SVG (overlay layer) -const arrow_svg = ` - -`; - -// Default layered stimuli - fish with arrow overlay -export const layered_stimuli = [ - fish_svg, - arrow_svg -]; - -// Fish only -export const fish_only = [ - fish_svg -]; - -// Arrow only -export const arrow_only = [ - arrow_svg -]; - -// Custom stimulus - you can add more layers -export const custom_stimulus = { - left: [ - '', - '' + - '' + - '' - ], - right: [ - '', - '' + - '' + - '' - ] -}; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css deleted file mode 100644 index 3ee61d23..00000000 --- a/packages/flanker-inhibitory/src/styles.css +++ /dev/null @@ -1,416 +0,0 @@ -/* Flanker Inhibitory Control Task Styles - Simple jsPsych approach */ - -/* Instructions */ -.flanker-instructions { - max-width: 90vw; - max-height: 85vh; - margin: 0 auto; - padding: 3vmin; - text-align: center; - line-height: 1.4; - background-color: white; - border-radius: 2vmin; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); - overflow-y: auto; -} - -.flanker-instructions h1 { - color: #2c3e50; - text-align: center; - margin-bottom: 2vmin; - font-size: 4.5vmin; -} - -.flanker-instructions h2 { - color: #34495e; - margin-top: 3vmin; - margin-bottom: 1.5vmin; - font-size: 3.5vmin; - text-align: center; -} - -.flanker-instructions p { - color: #555; - margin-bottom: 1.5vmin; - font-size: 3vmin; - text-align: center; -} - -.flanker-instructions .performance-note { - background-color: #e8f4fd; - border-left: 4px solid #3498db; - padding: 1.5vmin 2vmin; - margin: 2vmin 0; - font-weight: 500; - border-radius: 1vmin; -} - -.flanker-instructions .start-prompt { - text-align: center; - font-weight: 600; - color: #2c3e50; - margin-top: 2vmin; - font-size: 3.5vmin; -} - -.flanker-instructions ul { - padding-left: 3vmin; - margin: 2vmin 0; -} - -.flanker-instructions li { - margin: 1vmin 0; - font-size: 3vmin; - color: #555; - text-align: center; -} - -/* Performance summary */ -.performance-summary { - background-color: #f8f9fa; - border: 1px solid #dee2e6; - border-radius: 2vmin; - padding: 4vmin; - margin: 4vmin 0; - text-align: center; -} - -.performance-summary h3 { - color: #495057; - margin-bottom: 3vmin; - text-align: center; - font-size: 4.5vmin; -} - -.performance-summary p { - margin: 2vmin 0; - font-size: 3.5vmin; - color: #6c757d; - text-align: center; -} - -/* Trial display */ -.flanker-trial { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 50vh; - padding: 3vmin; - gap: 4vmin; -} - -.trial-prompt { - font-size: 3vmin; - font-weight: 200; - color: #2c3e50; - text-align: center; - max-width: 90vw; - line-height: normal; -} - -/* Flanker stimulus */ -.flanker-stim { - display: flex; - align-items: center; - justify-content: center; - margin: 3vmin 0; - padding: 3vmin; - width: 100%; - max-width: 95vw; - gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); - - /* Dynamic sizing based on number of stimuli and screen space */ - --base-size: min( - calc(85vw / var(--stimuli-count, 5)), - calc(70vh / 1), - 20vmin - ); - --min-size: 4vmin; - --max-size: 25vmin; - --svg-size: max(var(--min-size), min(var(--max-size), var(--base-size))); - - /* Special handling for many stimuli (> 7) to ensure readability */ - --many-stimuli-adjustment: calc(max(0, var(--stimuli-count, 5) - 7) * 0.5vmin); - --adjusted-svg-size: max(calc(var(--svg-size) + var(--many-stimuli-adjustment)), var(--min-size)); -} - -.flanker-stim span { - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - vertical-align: middle; - line-height: 0; -} - -.flanker-stim svg { - width: var(--adjusted-svg-size, var(--svg-size)); - height: var(--adjusted-svg-size, var(--svg-size)); - display: block; -} - -/* Practice highlighting */ -.flanker-stim.practice .center.highlighted { - background-color: #fff3cd; - border: 3px solid #ffc107; - border-radius: 50%; - padding: 1.5vmin; - animation: pulse 1.5s infinite; -} - -@keyframes pulse { - 0% { - box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); - } - 70% { - box-shadow: 0 0 0 2vmin rgba(255, 193, 7, 0); - } - 100% { - box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); - } -} - -/* Fixation cross */ -.fixation { - display: flex; - align-items: center; - justify-content: center; - min-height: 20vh; - font-size: 6vmin; - font-weight: bold; - color: #2c3e50; -} - -/* Feedback */ -.feedback { - display: flex; - align-items: center; - justify-content: center; - min-height: 20vh; - font-size: 6vmin; - font-weight: bold; - text-align: center; - padding: 3vmin; -} - -.feedback.correct { - color: #27ae60; -} - -.feedback.incorrect { - color: #e74c3c; -} - -/* ============================================================================ */ -/* JSPSYCH BUTTON OVERRIDES FOR MOBILE */ -/* ============================================================================ */ - -/* Button group layout */ -.jspsych-btn-group, -.jspsych-content .jspsych-btn-group, -#jspsych-html-button-response-btngroup { - display: flex !important; - flex-wrap: wrap !important; - gap: 3vmin !important; - max-width: 95vw !important; - margin: 4vmin auto !important; - justify-content: center !important; - align-items: center !important; -} - -/* All jsPsych buttons - mobile-friendly */ -.jspsych-btn { - border: 3px solid #3498db !important; - border-radius: 3vmin !important; - background-color: white !important; - color: #2c3e50 !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15) !important; - padding: 3vmin 6vmin !important; - margin: 1vmin !important; - font-family: inherit !important; - font-weight: bold !important; - font-size: 4vmin !important; - - /* Minimum touch target size */ - min-width: 15vmin !important; - min-height: 12vmin !important; -} - -/* Arrow response buttons - bigger for main task */ -.jspsych-btn:only-child { - /* Single buttons like Continue */ - min-width: 25vmin !important; - border-color: #27ae60 !important; - background-color: #2ecc71 !important; - color: white !important; -} - -/* For Left/Right arrow buttons (when there are exactly 2 buttons) */ -.jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - min-width: 20vmin !important; - min-height: 20vmin !important; - font-size: 8vmin !important; - flex: 1 1 auto !important; - max-width: 35vmin !important; -} - -/* Button hover states */ -.jspsych-btn:hover { - border-color: #2980b9 !important; - transform: translateY(-1vmin) !important; - box-shadow: 0 3vmin 4vmin rgba(0, 0, 0, 0.2) !important; - background-color: #ecf0f1 !important; -} - -.jspsych-btn:only-child:hover { - background-color: #27ae60 !important; - border-color: #229954 !important; - color: white !important; -} - -.jspsych-btn:active { - transform: translateY(0) !important; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.15) !important; -} - -/* ============================================================================ */ -/* RESPONSIVE DESIGN */ -/* ============================================================================ */ - -/* Small screens */ -@media (max-width: 480px) { - .flanker-stim { - gap: max(0.3vmin, calc(85vw / var(--stimuli-count, 5) * 0.08)); - padding: 2vmin; - - /* Tighter sizing for small screens */ - --base-size: min( - calc(80vw / var(--stimuli-count, 5)), - calc(50vh / 1), - 15vmin - ); - --min-size: 3vmin; - --max-size: 12vmin; - } - - .trial-prompt { - font-size: 4vmin; - } - - .fixation { - font-size: 10vmin; - min-height: 25vh; - } - - .jspsych-btn { - font-size: 3.5vmin !important; - } - - .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - font-size: 7vmin !important; - min-width: 18vmin !important; - min-height: 18vmin !important; - } -} - -/* Medium screens */ -@media (min-width: 481px) and (max-width: 768px) { - .flanker-stim { - gap: max(0.4vmin, calc(88vw / var(--stimuli-count, 5) * 0.09)); - - /* Medium screen sizing */ - --base-size: min( - calc(82vw / var(--stimuli-count, 5)), - calc(60vh / 1), - 18vmin - ); - --min-size: 3.5vmin; - --max-size: 18vmin; - } - - .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - font-size: 8vmin !important; - min-width: 22vmin !important; - min-height: 22vmin !important; - } -} - -/* Large screens */ -@media (min-width: 769px) { - .flanker-stim { - gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); - - /* Large screen sizing - more generous */ - --base-size: min( - calc(85vw / var(--stimuli-count, 5)), - calc(70vh / 1), - 22vmin - ); - --min-size: 4vmin; - --max-size: 30vmin; - } - - .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - font-size: 6vmin !important; - min-width: 15vmin !important; - min-height: 15vmin !important; - max-width: 20vmin !important; - } -} - -/* ============================================================================ */ -/* ACCESSIBILITY */ -/* ============================================================================ */ - -/* High contrast */ -@media (prefers-contrast: high) { - .flanker-stim svg { - filter: contrast(2); - } - - .performance-note { - background-color: #ffffff; - border-color: #000000; - color: #000000; - } - - .jspsych-btn { - border-color: #000000 !important; - background-color: #ffffff !important; - color: #000000 !important; - } -} - -/* Reduced motion */ -@media (prefers-reduced-motion: reduce) { - .flanker-stim.practice .center.highlighted { - animation: none; - } - - .jspsych-btn { - transition: none !important; - } -} - -/* Touch devices */ -@media (hover: none) and (pointer: coarse) { - .jspsych-btn:hover { - transform: none !important; - } - - .jspsych-btn:active { - background-color: #3498db !important; - color: white !important; - } - - .jspsych-btn:only-child:active { - background-color: #27ae60 !important; - color: white !important; - } -} \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts deleted file mode 100644 index 4e998cc1..00000000 --- a/packages/flanker-inhibitory/src/text.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* This file contains the configurable text used in the flanker inhibitory timeline. - * Researchers can modify these texts to change the language or instructions. - */ - -export const trial_text = { - // Button texts - continue_button: "Continue", - start_button: "Start", - ready_button: "I'm Ready", - end_button: "End", - practice_button: "Practice Again", - left_button: "←", - right_button: "→", - - // Task completion messages - task_complete_header: "Task Complete!", - task_complete_message: "Thank you for participating in the flanker inhibitory control task.", - - // Practice phase text - practice_header: "Practice Round", - practice_intro_message: "Let's practice! Look at the middle fish and click the button that shows which way it's swimming.", - practice_complete_header: "Practice Complete!", - practice_complete_message: "Great job! Now you're ready for the real task.", - practice_failed_message: "Let's try the practice again. Remember to focus on the middle fish only.", - - // Main task instructions and headers - main_task_header: "Main Task", - main_task_intro: "Now we'll start the real task. Remember to focus on the middle fish!", - main_task_prompt: "Look at the middle fish. Click the button for the direction it's swimming.", - - // Performance summary labels - performance_title: "Your Performance:", - accuracy_label: "Accuracy:", - response_time_label: "Average Response Time:", - - // Fixation and inter-trial - fixation_cross: "+", - - // Feedback messages - correct_feedback: "Correct!", - incorrect_feedback: "Incorrect", - too_slow_message: "Please respond faster", - - // Progress messages - block_progress: "Block {current} of {total}", - trial_progress: "Trial {current} of {total}", -}; - -// TTS (Text-to-Speech) configuration -export const tts_config = { - // Voice settings - voice_name: '', // Empty string uses default voice, or specify like 'Google US English' - rate: 1.0, // Speech rate (0.1 to 10) - pitch: 1.0, // Speech pitch (0 to 2) - volume: 1.0, // Speech volume (0 to 1) - - // Language and locale - lang: 'en-US', // Language code for speech synthesis - - // What text to speak aloud (can be disabled per element) - speak_instructions: true, // Speak instruction page content - speak_prompts: true, // Speak trial prompts - speak_feedback: true, // Speak correct/incorrect feedback - speak_completion: true, // Speak task completion messages - - // Auto-play settings - auto_speak_on_load: true, // Automatically start speaking when page loads - speak_button_text: false, // Whether to also speak button labels - - // Timing - speech_delay: 500, // Delay before starting speech (ms) - allow_skip: true, // Allow users to skip/stop speech -}; - -// Spoken versions of text (can be different from displayed text for clarity) -export const tts_text = { - // Instruction spoken versions - welcome_spoken: "Welcome to the Flanker Inhibitory Control Task. This is an attention and focus test.", - task_explanation_spoken: "You will see a group of fish swimming on the screen. Your job is to focus only on the middle fish and ignore the other fish around it.", - directions_spoken: "Look at which direction the middle fish is swimming and click the corresponding button. Click the left arrow if it's swimming left, or the right arrow if it's swimming right.", - - // Trial spoken versions - trial_prompt_spoken: "Look at the middle fish. Click the direction it's swimming.", - - // Feedback spoken versions - correct_spoken: "Correct", - incorrect_spoken: "Incorrect", - - // Practice spoken versions - practice_start_spoken: "Let's practice. Look at the middle fish and click the button that shows which way it's swimming.", - practice_complete_spoken: "Practice complete. Great job! Now you're ready for the real task.", - - // Main task spoken versions - main_task_start_spoken: "Now we'll start the real task. Remember to focus on the middle fish.", - task_complete_spoken: "Task complete. Thank you for participating.", - - // Controls - skip_speech_text: "Press space to skip speech", - speech_controls_note: "Speech can be skipped by pressing the space bar at any time.", -}; - -/* - * This is an array of page objects that have configurable texts for the instruction pages - * before the actual trials. Researchers can modify these instructions to change the - * task description, add new instruction pages, or translate to different languages. - * - * Each page object can contain: - * - header: Main title (displays in

) - * - header2: Subtitle (displays in

) - * - description: Main description text (displays in

) - * - task_explanation: Detailed task explanation (displays in

) - * - performance_note: Performance tips (displays in

) - * - strategy_title: Strategy section title (displays in

) - * - strategy_intro: Introduction to strategy (displays in

) - * - strategy_points: Array of strategy bullet points (displays as

  • ) - * - start_prompt: Final instruction before starting (displays in

    ) - * - buttons: Array of button text options - * - button_html: Custom HTML for buttons (optional) - */ - -export const instruction_pages = [ - { - header: "Flanker Inhibitory Control Task", - header2: "Attention and Focus Test", - description: "In this task, you will see a group of fish swimming on the screen.", - task_explanation: "Your job is to focus only on the middle fish and ignore the other fish around it.", - performance_note: "Look at which direction the middle fish is swimming and click the corresponding button.", - start_prompt: "Click continue to learn more about the task.", - buttons: ["Continue"], - }, - { - strategy_title: "Instructions", - strategy_intro: "For each trial:", - strategy_points: [ - "Look at the middle fish only - ignore the other fish", - "See which direction the middle fish is swimming", - "Click the ← button if it's swimming left", - "Click the → button if it's swimming right", - "Try to respond as quickly and accurately as possible" - ], - start_prompt: "The other fish around the middle fish might be swimming in different directions - don't let them distract you!", - buttons: ["Continue"], - }, - { - header2: "Button Instructions", - description: "Use these buttons to respond:", - task_explanation: "← Left Button = Middle fish swimming left
    → Right Button = Middle fish swimming right", - performance_note: "Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions.", - start_prompt: "Click continue to start the practice round.", - buttons: ["Continue"], - } -]; \ No newline at end of file diff --git a/packages/flanker-inhibitory/tsconfig.json b/packages/flanker-inhibitory/tsconfig.json deleted file mode 100644 index 33655f70..00000000 --- a/packages/flanker-inhibitory/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@jspsych/config/tsconfig.contrib.json", - "compilerOptions": { - "baseUrl": "." - }, - "include": [ - "src", - "../cli/src/build.js" - ] -} \ No newline at end of file diff --git a/packages/pattern-comparison-task/.claude/settings.local.json b/packages/pattern-comparison-task/.claude/settings.local.json deleted file mode 100644 index 55f2f229..00000000 --- a/packages/pattern-comparison-task/.claude/settings.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(rm:*)" - ], - "deny": [] - } -} \ No newline at end of file diff --git a/packages/pattern-comparison-task/README.md b/packages/pattern-comparison-task/README.md deleted file mode 100644 index 5cf57dc4..00000000 --- a/packages/pattern-comparison-task/README.md +++ /dev/null @@ -1,76 +0,0 @@ -# pattern-comparison-task - -## Overview - -An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same. - -## Loading - -### In browser - -```html - - - - - - - - - \ No newline at end of file diff --git a/packages/pattern-comparison-task/examples/index.html b/packages/pattern-comparison-task/examples/index.html deleted file mode 100644 index 8fa59489..00000000 --- a/packages/pattern-comparison-task/examples/index.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - Pattern Comparison Task - - - - - - - - - - \ No newline at end of file diff --git a/packages/pattern-comparison-task/jest.config.cjs b/packages/pattern-comparison-task/jest.config.cjs deleted file mode 100644 index bd1aacef..00000000 --- a/packages/pattern-comparison-task/jest.config.cjs +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/pattern-comparison-task/package.json b/packages/pattern-comparison-task/package.json deleted file mode 100644 index c22ca87a..00000000 --- a/packages/pattern-comparison-task/package.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "name": "@jspsych-timelines/pattern-comparison-task", - "version": "0.0.1", - "description": "An assessment of processing speed. Participants are asked to quickly determine whether two stimuli are the same or not the same.", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.browser.min.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelinePatternComparisonTask", - "test": "jest" - }, - "repository": { - "type": "git", - "url": "https://github.com/farhat60/jspsych-timelines/", - "directory": "packages/pattern-comparison-task" - }, - "keywords": [ - "jsPsych", - "pattern-comparison", - "processing-speed", - "cognitive-assessment" - ], - "author": { - "name": "Abdullah Hunter Farhat", - "url": "github.com/farhat60" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/farhat60/jspsych-timelines/issues" - }, - "homepage": "https://github.com/farhat60/jspsych-timelines/tree/main/packages/pattern-comparison-task#readme", - "devDependencies": { - "jspsych": "^8.0.0", - "@jspsych/config": "^3.2.2", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - } - } \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/index copy 4.ts b/packages/pattern-comparison-task/src/index copy 4.ts deleted file mode 100644 index e84747b0..00000000 --- a/packages/pattern-comparison-task/src/index copy 4.ts +++ /dev/null @@ -1,266 +0,0 @@ -import { JsPsych, TrialType } from "jspsych" -import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" -import { test_categories } from "./test-categories" - -interface TestCategory { - [testName: string]: [string, string] // [original_svg, edited_svg] -} - -interface PatternComparisonConfig { - /** Array of three test categories, each containing test pairs */ - test_categories?: TestCategory[] - /** Number of trials to generate */ - numTrials?: number - /** Instructions text to display above each trial */ - instructions?: string - /** Enable text-to-speech for instructions */ - enableTTS?: boolean - /** Text for the "same" button */ - sameButtonText?: string - /** Text for the "different" button */ - differentButtonText?: string - /** Maximum time allowed per trial (in ms) */ - trialTimeout?: number - /** Inter-trial interval (in ms) */ - interTrialInterval?: number -} - -// Default test categories based on the PMC article methodology - -function generateTrials(config: PatternComparisonConfig) { - const test_SVG = config.test_categories || test_categories; - const numTrials = config.numTrials || 20; - const trials = []; - - for (let i = 0; i < numTrials; i++) { - // Randomly select a category - const categoryIndex = Math.floor(Math.random() * test_SVG.length); - const selectedCategory = test_SVG[categoryIndex]; - - // Randomly select a test within the category - const testNames = Object.keys(selectedCategory); - const testName = testNames[Math.floor(Math.random() * testNames.length)]; - const [originalSvg, editedSvg] = selectedCategory[testName]; - - // Randomly decide if patterns should be same or different - const isSame = Math.random() < 0.5; - - const pattern1 = originalSvg; - const pattern2 = isSame ? originalSvg : editedSvg; - - trials.push({ - pattern1, - pattern2, - correctAnswer: isSame ? 0 : 1, // 0 for same, 1 for different - categoryIndex, - testName, - isSame - }); - } - - return trials; -} - -export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig = {}) { - const { - instructions = "Are these two patterns the same?", - enableTTS = false, - sameButtonText = "Same", - differentButtonText = "Different", - trialTimeout = 10000, - interTrialInterval = 500 - } = config - - const trials = generateTrials(config) - const timeline = [] - - // Instructions screen - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -

    -

    Pattern Comparison Task

    -

    You will see two patterns side by side. Your task is to decide whether they are the same or different.

    -

    Look carefully at both patterns and compare them closely.

    -

    Respond as quickly and accurately as possible.

    -

    Click the button below to start.

    -
    - `, - choices: ['Start'], - margin_horizontal: '10px', - margin_vertical: '10px' - }) - - // Create trial timeline - trials.forEach((trial, index) => { - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` - -
    -
    ${instructions}
    -
    -
    ${trial.pattern1}
    -
    ${trial.pattern2}
    -
    -
    - `, - choices: [sameButtonText, differentButtonText], - margin_horizontal: '20px', - margin_vertical: '15px', - button_html: function(choice, choice_index) { - return ``; - }, - trial_duration: trialTimeout, - data: { - task: 'pattern-comparison', - trial_number: index + 1, - correct_answer: trial.correctAnswer, - category_index: trial.categoryIndex, - test_name: trial.testName, - is_same: trial.isSame, - pattern1: trial.pattern1, - pattern2: trial.pattern2 - }, - on_finish: function(data: any) { - data.correct = data.response === data.correct_answer - data.reaction_time = data.rt - }, - on_start: function() { - if (enableTTS && 'speechSynthesis' in window) { - const utterance = new SpeechSynthesisUtterance(instructions) - speechSynthesis.speak(utterance) - } - } - }) - - // Inter-trial interval - if (index < trials.length - 1) { - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: '
    +
    ', - choices: [], - trial_duration: interTrialInterval - }) - } - }) - - // End screen - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
    -

    Task Complete!

    -

    Thank you for participating in the pattern comparison task.

    -
    - `, - choices: ['Continue'] - }) - - return timeline -} - -export const timelineUnits = { - instructions: "Instructions for the pattern comparison task", - trial: "Single pattern comparison trial", - interTrialInterval: "Fixation cross between trials", - endScreen: "Task completion screen" -} - -export const utils = { - generateTrials, - /** Calculate accuracy and reaction time statistics by category */ - calculatePerformance: function(data: any[]) { - const trialData = data.filter(d => d.task === 'pattern-comparison') - const correct = trialData.filter(d => d.correct).length - const total = trialData.length - const accuracy = total > 0 ? (correct / total) * 100 : 0 - - const validRTs = trialData.filter(d => d.correct && d.rt !== null).map(d => d.rt) - const meanRT = validRTs.length > 0 ? validRTs.reduce((a, b) => a + b, 0) / validRTs.length : null - - // Calculate performance by category - const categoryPerformance = [0, 1, 2].map(categoryIndex => { - const categoryTrials = trialData.filter(d => d.category_index === categoryIndex) - const categoryCorrect = categoryTrials.filter(d => d.correct).length - const categoryTotal = categoryTrials.length - const categoryAccuracy = categoryTotal > 0 ? (categoryCorrect / categoryTotal) * 100 : 0 - - const categoryValidRTs = categoryTrials.filter(d => d.correct && d.rt !== null).map(d => d.rt) - const categoryMeanRT = categoryValidRTs.length > 0 ? - categoryValidRTs.reduce((a, b) => a + b, 0) / categoryValidRTs.length : null - - return { - categoryIndex, - accuracy: categoryAccuracy, - meanReactionTime: categoryMeanRT, - totalTrials: categoryTotal, - correctTrials: categoryCorrect - } - }) - - return { - overall: { - accuracy, - meanReactionTime: meanRT, - totalTrials: total, - correctTrials: correct - }, - byCategory: categoryPerformance - } - } -} - -// Default export for convenience -export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/index.spec.ts b/packages/pattern-comparison-task/src/index.spec.ts deleted file mode 100644 index 823ee7ca..00000000 --- a/packages/pattern-comparison-task/src/index.spec.ts +++ /dev/null @@ -1,414 +0,0 @@ -import { JsPsych } from "jspsych"; -import { createTimeline, utils, timelineUnits, trial_text, instruction_pages } from "./index"; -import { test_categories } from "./test-categories"; - -// Mock jsPsych instance -const mockJsPsych = {} as JsPsych; - -// Mock Web Speech API -const mockSpeechSynthesis = { - cancel: jest.fn(), - speak: jest.fn(), -}; - -const mockSpeechSynthesisUtterance = jest.fn().mockImplementation((text) => ({ - text, - rate: 0.8, - volume: 0.8, -})); - -// Set up global mocks -global.speechSynthesis = mockSpeechSynthesis as any; -global.SpeechSynthesisUtterance = mockSpeechSynthesisUtterance as any; -global.window = { speechSynthesis: mockSpeechSynthesis } as any; - -// Mock Math.random for predictable tests -const mockMath = Object.create(global.Math); -mockMath.random = jest.fn(); -global.Math = mockMath; - -describe("Pattern Comparison Task", () => { - beforeEach(() => { - jest.clearAllMocks(); - mockMath.random.mockReturnValue(0.5); - }); - - describe("createTimeline", () => { - it("should create a basic timeline with default configuration", () => { - const timeline = createTimeline(mockJsPsych); - - expect(timeline).toBeDefined(); - expect(timeline.timeline).toBeInstanceOf(Array); - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - - it("should create timeline with custom number of trials", () => { - const customTrials = 5; - const timeline = createTimeline(mockJsPsych, { num_trials: customTrials }); - - // Timeline should have (trials * 2 - 1) + 1 = trials + inter-trial intervals + end screen - // For 5 trials: 5 trials + 4 inter-trial intervals + 1 end screen = 10 items - expect(timeline.timeline).toHaveLength(10); - }); - - it("should include instructions when show_instructions is true", () => { - const timeline = createTimeline(mockJsPsych, { show_instructions: true }); - - expect(timeline.timeline).toHaveLength(2); // instructions + main task - expect(timeline.timeline[0].timeline).toBeDefined(); // instructions timeline - expect(timeline.timeline[1].timeline).toBeDefined(); // main task timeline - }); - - it("should use custom button text", () => { - const customConfig = { - same_button_text: "Identical", - different_button_text: "Not Identical", - num_trials: 1 - }; - const timeline = createTimeline(mockJsPsych, customConfig); - - const firstTrial = timeline.timeline[0]; - expect(firstTrial.choices).toEqual(["Identical", "Not Identical"]); - }); - - it("should use custom prompt text", () => { - const customPrompt = "Do these patterns match?"; - const timeline = createTimeline(mockJsPsych, { - prompt: customPrompt, - num_trials: 1 - }); - - const firstTrial = timeline.timeline[0]; - expect(firstTrial.stimulus).toContain(customPrompt); - }); - - it("should set custom trial timeout", () => { - const customTimeout = 5000; - const timeline = createTimeline(mockJsPsych, { - trial_timeout: customTimeout, - num_trials: 1 - }); - - const firstTrial = timeline.timeline[0]; - expect(firstTrial.trial_duration).toBe(customTimeout); - }); - - it("should handle custom test categories", () => { - const customCategories = [ - { - "test_shape": [ - '', - '' - ] - } - ]; - - const timeline = createTimeline(mockJsPsych, { - test_categories: customCategories, - num_trials: 1 - }); - - const firstTrial = timeline.timeline[0]; - expect(firstTrial.data.category_index).toBe(0); - expect(firstTrial.data.test_name).toBe("test_shape"); - }); - }); - - describe("utils.generateTrials", () => { - it("should generate the correct number of trials", () => { - const trials = utils.generateTrials({ num_trials: 10 }); - expect(trials).toHaveLength(10); - }); - - it("should generate trials with required properties", () => { - const trials = utils.generateTrials({ num_trials: 1 }); - const trial = trials[0]; - - expect(trial).toHaveProperty("pattern1"); - expect(trial).toHaveProperty("pattern2"); - expect(trial).toHaveProperty("correct_answer"); - expect(trial).toHaveProperty("category_index"); - expect(trial).toHaveProperty("test_name"); - expect(trial).toHaveProperty("is_same"); - }); - - it("should generate 'same' trials when patterns are identical", () => { - mockMath.random.mockReturnValue(0.3); // < 0.5, so is_same = true - - const trials = utils.generateTrials({ num_trials: 1 }); - const trial = trials[0]; - - expect(trial.is_same).toBe(true); - expect(trial.correct_answer).toBe(0); - expect(trial.pattern1).toBe(trial.pattern2); - }); - - it("should generate 'different' trials when patterns differ", () => { - mockMath.random.mockReturnValue(0.7); // > 0.5, so is_same = false - - const trials = utils.generateTrials({ num_trials: 1 }); - const trial = trials[0]; - - expect(trial.is_same).toBe(false); - expect(trial.correct_answer).toBe(1); - expect(trial.pattern1).not.toBe(trial.pattern2); - }); - - it("should use custom test categories", () => { - const customCategories = [ - { - "custom_test": [ - 'original', - 'modified' - ] - } - ]; - - const trials = utils.generateTrials({ - test_categories: customCategories, - num_trials: 1 - }); - - expect(trials[0].test_name).toBe("custom_test"); - expect(trials[0].category_index).toBe(0); - }); - }); - - describe("utils.calculatePerformance", () => { - const mockTrialData = [ - { task: 'pattern-comparison', correct: true, rt: 1000, category_index: 0 }, - { task: 'pattern-comparison', correct: false, rt: 1500, category_index: 0 }, - { task: 'pattern-comparison', correct: true, rt: 800, category_index: 1 }, - { task: 'pattern-comparison', correct: true, rt: null, category_index: 1 }, - { task: 'instruction-page', correct: true, rt: 500, category_index: 0 }, // Should be filtered out - ]; - - it("should calculate overall performance correctly", () => { - const performance = utils.calculatePerformance(mockTrialData); - - expect(performance.overall.total_trials).toBe(4); - expect(performance.overall.correct_trials).toBe(3); - expect(performance.overall.accuracy).toBe(75); - expect(performance.overall.mean_reaction_time).toBe(900); // (1000 + 800) / 2 - }); - - it("should calculate performance by category", () => { - const performance = utils.calculatePerformance(mockTrialData); - - // Category 0: 1 correct out of 2 trials - expect(performance.by_category[0].accuracy).toBe(50); - expect(performance.by_category[0].total_trials).toBe(2); - expect(performance.by_category[0].correct_trials).toBe(1); - expect(performance.by_category[0].mean_reaction_time).toBe(1000); - - // Category 1: 2 correct out of 2 trials (one with null rt) - expect(performance.by_category[1].accuracy).toBe(100); - expect(performance.by_category[1].total_trials).toBe(2); - expect(performance.by_category[1].correct_trials).toBe(2); - expect(performance.by_category[1].mean_reaction_time).toBe(800); // Only non-null rt - }); - - it("should handle empty data", () => { - const performance = utils.calculatePerformance([]); - - expect(performance.overall.accuracy).toBe(0); - expect(performance.overall.mean_reaction_time).toBe(null); - expect(performance.overall.total_trials).toBe(0); - }); - - it("should filter out non-task data", () => { - const mixedData = [ - { task: 'pattern-comparison', correct: true, rt: 1000, category_index: 0 }, - { task: 'instruction-page', correct: true, rt: 500, category_index: 0 }, - { task: 'something-else', correct: false, rt: 2000, category_index: 0 }, - ]; - - const performance = utils.calculatePerformance(mixedData); - expect(performance.overall.total_trials).toBe(1); - }); - }); - - describe("utils.speakText", () => { - it("should cancel existing speech and speak new text", () => { - utils.speakText("Hello world"); - - expect(mockSpeechSynthesis.cancel).toHaveBeenCalled(); - expect(mockSpeechSynthesisUtterance).toHaveBeenCalledWith("Hello world"); - expect(mockSpeechSynthesis.speak).toHaveBeenCalled(); - }); - - it("should set utterance properties correctly", () => { - utils.speakText("Test text"); - - const utteranceCall = mockSpeechSynthesisUtterance.mock.calls[0]; - expect(utteranceCall[0]).toBe("Test text"); - }); - - it("should not throw error when speechSynthesis is not available", () => { - const originalWindow = global.window; - global.window = {} as any; - - expect(() => utils.speakText("Test")).not.toThrow(); - - global.window = originalWindow; - }); - }); - - describe("utils.createInstructions", () => { - it("should create instruction timeline with default pages", () => { - const instructions = utils.createInstructions(); - - expect(instructions.timeline).toBeInstanceOf(Array); - expect(instructions.timeline.length).toBe(instruction_pages.length); - }); - - it("should create instruction timeline with custom pages", () => { - const customPages = [ - { - header: "Custom Header", - header2: "Custom Subheader", - description: "Custom description", - task_explanation: "Custom task explanation", - performance_note: "Custom performance note", - start_prompt: "Click to continue", - buttons: ["Next"] - } - ]; - - const instructions = utils.createInstructions(customPages); - - expect(instructions.timeline).toHaveLength(1); - expect(instructions.timeline[0].stimulus).toContain("Custom Header"); - expect(instructions.timeline[0].stimulus).toContain("Custom description"); - expect(instructions.timeline[0].choices).toEqual(["Next"]); - }); - - it("should enable TTS when requested", () => { - const customPages = [ - { - header: "Test Header", - header2: "Test Subheader", - description: "Test description", - task_explanation: "Test explanation", - performance_note: "Test note", - start_prompt: "Continue", - buttons: ["Continue"] - } - ]; - - const instructions = utils.createInstructions(customPages, true); - const instructionPage = instructions.timeline[0]; - - expect(instructionPage.on_start).toBeDefined(); - expect(typeof instructionPage.on_start).toBe("function"); - }); - - it("should handle pages with strategy points", () => { - const customPages = [ - { - strategy_title: "Strategy", - strategy_intro: "Follow these steps:", - strategy_points: ["Step 1", "Step 2", "Step 3"], - start_prompt: "Ready to begin", - buttons: ["Got it"] - } - ]; - - const instructions = utils.createInstructions(customPages); - const page = instructions.timeline[0]; - - expect(page.stimulus).toContain("Strategy"); - expect(page.stimulus).toContain("Step 1"); - expect(page.stimulus).toContain("Step 2"); - expect(page.stimulus).toContain("Step 3"); - }); - - it("should handle missing optional fields gracefully", () => { - const minimalPages = [ - { - header: "", - header2: "", - description: "", - task_explanation: "", - performance_note: "", - start_prompt: "", - buttons: ["Continue"] - } - ]; - - const instructions = utils.createInstructions(minimalPages); - expect(instructions.timeline).toHaveLength(1); - expect(instructions.timeline[0].stimulus).toBeDefined(); - }); - }); - - describe("Exports", () => { - it("should export trial_text configuration", () => { - expect(trial_text).toBeDefined(); - expect(trial_text.same_button).toBe("Same"); - expect(trial_text.different_button).toBe("Different"); - expect(trial_text.prompt).toBe("Are these two patterns the same?"); - }); - - it("should export instruction_pages configuration", () => { - expect(instruction_pages).toBeDefined(); - expect(instruction_pages).toBeInstanceOf(Array); - expect(instruction_pages.length).toBeGreaterThan(0); - }); - - it("should export timelineUnits descriptions", () => { - expect(timelineUnits).toBeDefined(); - expect(timelineUnits.instructions).toBeDefined(); - expect(timelineUnits.trial).toBeDefined(); - expect(timelineUnits.interTrialInterval).toBeDefined(); - expect(timelineUnits.endScreen).toBeDefined(); - }); - - it("should export utils object with required functions", () => { - expect(utils).toBeDefined(); - expect(typeof utils.generateTrials).toBe("function"); - expect(typeof utils.createInstructions).toBe("function"); - expect(typeof utils.speakText).toBe("function"); - expect(typeof utils.calculatePerformance).toBe("function"); - }); - }); - - describe("Trial Data Structure", () => { - it("should create trials with correct data structure", () => { - const timeline = createTimeline(mockJsPsych, { num_trials: 1 }); - const trial = timeline.timeline[0]; - - expect(trial.data).toHaveProperty("task", "pattern-comparison"); - expect(trial.data).toHaveProperty("trial_number", 1); - expect(trial.data).toHaveProperty("correct_answer"); - expect(trial.data).toHaveProperty("category_index"); - expect(trial.data).toHaveProperty("test_name"); - expect(trial.data).toHaveProperty("is_same"); - expect(trial.data).toHaveProperty("pattern1"); - expect(trial.data).toHaveProperty("pattern2"); - }); - - it("should set correct answer based on trial type", () => { - // Test same trial - mockMath.random.mockReturnValue(0.3); // is_same = true - let timeline = createTimeline(mockJsPsych, { num_trials: 1 }); - expect(timeline.timeline[0].data.correct_answer).toBe(0); - - // Test different trial - mockMath.random.mockReturnValue(0.7); // is_same = false - timeline = createTimeline(mockJsPsych, { num_trials: 1 }); - expect(timeline.timeline[0].data.correct_answer).toBe(1); - }); - }); - - describe("Integration with test_categories", () => { - it("should use default test categories when none provided", () => { - const timeline = createTimeline(mockJsPsych, { num_trials: 1 }); - const trial = timeline.timeline[0]; - - expect(trial.data.category_index).toBeGreaterThanOrEqual(0); - expect(trial.data.category_index).toBeLessThan(test_categories.length); - expect(trial.data.test_name).toBeDefined(); - }); - }); -}); \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/index.ts b/packages/pattern-comparison-task/src/index.ts deleted file mode 100644 index 654a7b10..00000000 --- a/packages/pattern-comparison-task/src/index.ts +++ /dev/null @@ -1,314 +0,0 @@ -import { JsPsych, TrialType } from "jspsych" -import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" -import { test_categories } from "./test-categories" -import { trial_text, instruction_pages } from "./text" - -interface PatternComparisonConfig { - /** Array of test categories, each containing test pairs */ - test_categories?: any[] - /** Number of trials to generate */ - num_trials?: number - /** Instructions text to display above each trial */ - prompt?: string - /** Enable text-to-speech for instructions and prompts */ - enable_tts?: boolean - /** Text for the "same" button */ - same_button_text?: string - /** Text for the "different" button */ - different_button_text?: string - /** Maximum time allowed per trial (in ms) */ - trial_timeout?: number - /** Inter-trial interval (in ms) */ - inter_trial_interval?: number - /** Show instruction pages before the task */ - show_instructions?: boolean - /** Custom instruction texts */ - instruction_texts?: typeof instruction_pages -} - -function speakText(text: string) { - if ('speechSynthesis' in window) { - // Stop any ongoing speech - speechSynthesis.cancel(); - - // Create and speak the utterance - const utterance = new SpeechSynthesisUtterance(text); - utterance.rate = 0.8; // Slightly slower for clarity - utterance.volume = 0.8; - speechSynthesis.speak(utterance); - } -} - -function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false) { - const instruction_timeline = []; - - // Create each instruction page as a separate trial - instruction_pages_data.forEach((page_data, page_index) => { - - // Build page text for TTS - const page_text = [ - page_data.header, - page_data.header2, - page_data.description, - page_data.task_explanation, - page_data.performance_note, - page_data.strategy_title, - page_data.strategy_intro, - ...(page_data.strategy_points || []), - page_data.start_prompt, - ].filter(Boolean).join(' '); //remove undefined strings by filtering out falsy values - - instruction_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
    -

    ${page_data.header || ""}

    -

    ${page_data.header2 || ""}

    -

    ${page_data.description || ""}

    -

    ${page_data.task_explanation || ""}

    -

    ${page_data.performance_note || ""}

    - ${page_data.strategy_title ? `

    ${page_data.strategy_title}

    ` : ""} - ${page_data.strategy_intro ? `

    ${page_data.strategy_intro}

    ` : ""} - ${page_data.strategy_points ? ` -
      - ${page_data.strategy_points.map(point => `
    • ${point}
    • `).join('')} -
    - ` : ""} - ${page_data.start_prompt ? `

    ${page_data.start_prompt}

    ` : ""} -
    - `, - choices: page_data.buttons, - margin_horizontal: '15px', - margin_vertical: '10px', - button_html: function(choice, choice_index) { - // Check if custom button HTML is provided for this page - if (page_data.button_html && page_data.button_html[choice_index]) { - return page_data.button_html[choice_index].replace('{choice}', choice); - } - // Default button styling - return ``; - }, - on_start: enable_tts ? function() { - // Stop any ongoing speech first - speechSynthesis.cancel(); - // Speak the page content when page starts - if (page_text.trim()) { - speakText(page_text); - } - } : undefined, - on_finish: function(data: any) { - // Stop speech when moving to next page - speechSynthesis.cancel(); - }, - data: { - page_index: page_index, - task: 'instruction-page' - } - }); - }); - - return { - timeline: instruction_timeline - }; -} - -function generateTrials(config: PatternComparisonConfig) { - const test_svg = config.test_categories || test_categories; - const num_trials = config.num_trials || 20; - const trials = []; - - for (let i = 0; i < num_trials; i++) { - // Randomly select a category - const category_index = Math.floor(Math.random() * test_svg.length); - const selected_category = test_svg[category_index]; - - // Randomly select a test within the category - const test_names = Object.keys(selected_category); - const test_name = test_names[Math.floor(Math.random() * test_names.length)]; - const [original_svg, edited_svg] = selected_category[test_name]; - - // Randomly decide if patterns should be same or different - const is_same = Math.random() < 0.5; - - const pattern1 = original_svg; - const pattern2 = is_same ? original_svg : edited_svg; - - trials.push({ - pattern1, - pattern2, - correct_answer: is_same ? 0 : 1, // 0 for same, 1 for different - category_index, - test_name, - is_same - }); - } - - return trials; -} - -export function createTimeline(jsPsych: JsPsych, config: PatternComparisonConfig = {}) { - const { - prompt = trial_text.prompt, - enable_tts = false, - same_button_text = trial_text.same_button, - different_button_text = trial_text.different_button, - trial_timeout = 10000, - inter_trial_interval = 500, - show_instructions = false, - instruction_texts = instruction_pages - } = config - - const trials = generateTrials(config) - const timeline = [] - - - // Create trial timeline - trials.forEach((trial, index) => { - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
    -
    ${prompt}
    -
    -
    ${trial.pattern1}
    -
    ${trial.pattern2}
    -
    -
    - `, - choices: [same_button_text, different_button_text], - margin_horizontal: '20px', - margin_vertical: '15px', - button_html: function(choice, choice_index) { - return ``; - }, - trial_duration: trial_timeout, - data: { - task: 'pattern-comparison', - trial_number: index + 1, - correct_answer: trial.correct_answer, - category_index: trial.category_index, - test_name: trial.test_name, - is_same: trial.is_same, - pattern1: trial.pattern1, - pattern2: trial.pattern2 - }, - on_finish: function(data: any) { - data.correct = data.response === data.correct_answer - data.reaction_time = data.rt - // Stop any ongoing speech when trial ends - speechSynthesis.cancel(); - }, - on_start: function() { - if (enable_tts) { - speakText(prompt); - } - } - }) - - // Inter-trial interval - if (index < trials.length - 1) { - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: `
    ${trial_text.fixation_cross}
    `, - choices: [], - trial_duration: inter_trial_interval - }) - } - }) - - // End screen - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
    -

    ${trial_text.task_complete_header}

    -

    ${trial_text.task_complete_message}

    -
    - `, - choices: ["End"], - button_html: function(choice, choice_HTML) { //this is supposed to return the HTML for the each respective button. - return ``; - } - }) - - // Create the main task timeline - const task_timeline = { - timeline: timeline - }; - - // Return complete timeline with or without detailed instructions - if (show_instructions) { - const detailed_instructions = createInstructions(instruction_texts, enable_tts); - - const nested_timeline = { - timeline: [detailed_instructions, task_timeline] - }; - return nested_timeline; - - } else { - return task_timeline; - } -} - -/** Calculate accuracy and reaction time statistics by category */ -function calculatePerformance(data: any[]) { - const trial_data = data.filter(d => d.task === 'pattern-comparison') - const correct = trial_data.filter(d => d.correct).length - const total = trial_data.length - const accuracy = total > 0 ? (correct / total) * 100 : 0 - - const valid_rts = trial_data.filter(d => d.correct && d.rt !== null).map(d => d.rt) - const mean_rt = valid_rts.length > 0 ? valid_rts.reduce((a, b) => a + b, 0) / valid_rts.length : null - - // Calculate performance by category - const category_performance = [0, 1, 2].map(category_index => { - const category_trials = trial_data.filter(d => d.category_index === category_index) - const category_correct = category_trials.filter(d => d.correct).length - const category_total = category_trials.length - const category_accuracy = category_total > 0 ? (category_correct / category_total) * 100 : 0 - - const category_valid_rts = category_trials.filter(d => d.correct && d.rt !== null).map(d => d.rt) - const category_mean_rt = category_valid_rts.length > 0 ? - category_valid_rts.reduce((a, b) => a + b, 0) / category_valid_rts.length : null - - return { - category_index, - accuracy: category_accuracy, - mean_reaction_time: category_mean_rt, - total_trials: category_total, - correct_trials: category_correct - } - }) - - return { - overall: { - accuracy, - mean_reaction_time: mean_rt, - total_trials: total, - correct_trials: correct - }, - by_category: category_performance - } -} - -//gotta check standardization of these exports - -export const timelineUnits = { - instructions: "Instructions for the pattern comparison task", - trial: "Single pattern comparison trial", - interTrialInterval: "Fixation cross between trials", - endScreen: "Task completion screen" -} - -export const utils = { - generateTrials, - createInstructions, - speakText, - calculatePerformance -} - -// Export text configuration for external use -export { trial_text, instruction_pages, createInstructions } - -// Default export for convenience -export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/styles.css b/packages/pattern-comparison-task/src/styles.css deleted file mode 100644 index 40fe037a..00000000 --- a/packages/pattern-comparison-task/src/styles.css +++ /dev/null @@ -1,159 +0,0 @@ -/* Pattern Comparison Task Styles */ - -/* Instruction page styles */ -.pattern-instructions-container { - max-width: 700px; - margin: 0 auto; - text-align: center; - padding: 40px 20px; - font-family: Arial, sans-serif; -} - -.pattern-instructions-container h1 { - color: #2196F3; - margin-bottom: 10px; -} - -.pattern-instructions-container h2 { - color: #666; - margin-bottom: 20px; -} - -.pattern-instructions-container h2.strategy-title { - color: #2196F3; - margin-top: 30px; - margin-bottom: 15px; -} - -.pattern-instructions-container p { - font-size: 18px; - line-height: 1.6; - margin-bottom: 15px; -} - -.pattern-instructions-container p.performance-note { - font-size: 16px; - color: #555; - margin-bottom: 20px; -} - -.pattern-instructions-container p.strategy-intro { - font-size: 16px; - margin-bottom: 10px; -} - -.pattern-instructions-container p.start-prompt { - font-weight: bold; - color: #2196F3; - margin-top: 30px; - font-size: 16px; -} - -.pattern-instructions-container ul { - text-align: left; - display: inline-block; - font-size: 16px; - line-height: 1.8; -} - -/* Button styles */ -.pattern-start-button { - font-size: 16px; - padding: 12px 24px; - margin: 0 10px; - min-width: 120px; - background-color: #4CAF50; - color: white; - border: none; - border-radius: 5px; -} - -.pattern-continue-button { - font-size: 16px; - padding: 12px 24px; - margin: 0 10px; - min-width: 120px; - background-color: #2196F3; - color: white; - border: none; - border-radius: 5px; -} - -.pattern-trial-button { - font-size: 16px; - padding: 12px 24px; - margin: 0 10px; - min-width: 100px; -} - -/* Trial styles */ -.pattern-comparison-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 60vh; - padding: 20px; - font-family: Arial, sans-serif; -} - -.pattern-instructions { - font-size: 18px; - margin-bottom: 30px; - text-align: center; -} - -.patterns-container { - display: flex; - align-items: center; - justify-content: center; - gap: 50px; - margin-bottom: 30px; - flex-wrap: wrap; -} - -.pattern { - display: flex; - align-items: center; - justify-content: center; - padding: 20px; - border: 2px solid #ccc; - border-radius: 10px; - background-color: #f9f9f9; - width: 150px; - height: 150px; -} - -.pattern svg { - max-width: 100%; - max-height: 100%; -} - -/* End screen styles */ -.pattern-end-screen { - text-align: center; - padding: 40px; -} - -/* Fixation cross styles */ -.pattern-fixation { - font-size: 24px; - text-align: center; -} - -/* Responsive styles */ -@media (max-width: 768px) { - .patterns-container { - flex-direction: column; - gap: 30px; - } - - .pattern { - width: 120px; - height: 120px; - } - - .pattern-instructions { - font-size: 16px; - } -} \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/test-categories.ts b/packages/pattern-comparison-task/src/test-categories.ts deleted file mode 100644 index 44ba1897..00000000 --- a/packages/pattern-comparison-task/src/test-categories.ts +++ /dev/null @@ -1,184 +0,0 @@ -// The first SVG in every array represents the original object, while the second SVG represents a variation of that object. -// This is useful bc you will either have the original object twice when is_same = true or original and variation when is_same = false. - -export const test_categories = [ - // Category 1: Color discrimination - Different objects with fill variations - { - "smiley_face": [ - ` - - - - - `, - ` - - - - - ` - ], - "heart_shape": [ - ` - - `, - ` - - ` - ], - "star_shape": [ - ` - - `, - ` - - ` - ], - "circle_shape": [ - ` - - `, - ` - - ` - ] - }, - // Category 2: Adding/taking elements - Objects where you can add/remove parts - { - "tree_bare": [ - ` - - - - `, - ` - - - `, - ], - "house_building": [ - ` - - - - - - `, - ` - - - - - ` - ], - "clock_time": [ - ` - - - `, - ` - - - - - ` - ], - "face_glasses": [ - ` - - - - - `, - ` - - - - - - - - ` - ] - }, - // Category 3: Quantity variation - Different objects in different quantities - { - "gift_box": [ - ` - - - - `, - ` - - - - - - - - - - - - - - - - ` - ], - "apple_fruit": [ - ` - - - - `, - ` - - - - - - - - - ` - ], - "car_vehicle": [ - ` - - - - - `, - ` - - - - - - - - - - - ` - ] - } -] \ No newline at end of file diff --git a/packages/pattern-comparison-task/src/text.ts b/packages/pattern-comparison-task/src/text.ts deleted file mode 100644 index d30ee0d7..00000000 --- a/packages/pattern-comparison-task/src/text.ts +++ /dev/null @@ -1,60 +0,0 @@ -/* This file contains the configurable text used in the pattern comparison timeline. -*/ -export const trial_text = { - same_button: "Same", - different_button: "Different", - prompt: "Are these two patterns the same?", - task_complete_header: "Task Complete!", - task_complete_message: "Thank you for participating in the pattern comparison task.", - fixation_cross: "+", -} - -/* -* This is an array of page objects that have configurable texts for the instruction pages before the actual trials -* You can make as many pages as you would like, just add a new array and your text (e.g. ,{header2: "new page here"}) -* "page" objects are separated by commas, just as in any array. -* keys, in the order they show on a page: header, header2, description, task_explanation, performance_note, -strategy_title, strategy_intro, strategy_points (array of strings), start_prompt -* You can leave any keys or text options out if you do not want them in any specific page -*/ - -export const instruction_pages = [{ //example page, you can remove any of the keys you do not want to use - header: "header1 in

    ", - header2: "header2 in

    ", - description: "This is a description of the task in

    .", - task_explanation: "More detailed explanation of the task in

    .", - performance_note: "Note about performance in

    .", - strategy_title: "Strategy Title in

    ", - strategy_intro: "Introduction to the strategy in

    .", - strategy_points: [ - "Point 1 about the strategy", - "Point 2 about the strategy", - "Point 3 about the strategy" - ], - start_prompt: "Click continue to start the task.", - buttons: ["button text", "All buttons will just move onto next page", "they will be registered in data though!"], - button_html: ["", ""], -}, - { - header: "Pattern Comparison Task", //

    - header2: "Visual Pattern Recognition", //

    - description: "In this task, you will see two visual patterns displayed side by side.", //

    - task_explanation: "Your job is to determine whether the two patterns are exactly the same or different.", //

    - performance_note: "Try to respond as quickly and accurately as possible.", //

    - - start_prompt: "Click continue to move to the next page.", //

    in bold and blue - buttons: ["Continue"], -}, //page two, you can add more pages if needed by creating more objects in the array -{ - strategy_title: "Instructions", - strategy_intro: "For each trial:", - strategy_points: [ - "Look carefully at both patterns", - "Compare all visual elements (shape, color, size, quantity)", - "Click 'Same' if patterns are identical", - "Click 'Different' if patterns vary in any way", - "Respond as quickly and accurately as possible" - ], - start_prompt: "Click continue to start the task.", - buttons: ["Start"], -}]; \ No newline at end of file diff --git a/packages/pattern-comparison-task/tsconfig.json b/packages/pattern-comparison-task/tsconfig.json deleted file mode 100644 index 33655f70..00000000 --- a/packages/pattern-comparison-task/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@jspsych/config/tsconfig.contrib.json", - "compilerOptions": { - "baseUrl": "." - }, - "include": [ - "src", - "../cli/src/build.js" - ] -} \ No newline at end of file diff --git a/packages/spatial-cueing/CHANGELOG.md b/packages/spatial-cueing/CHANGELOG.md deleted file mode 100644 index a9dc3552..00000000 --- a/packages/spatial-cueing/CHANGELOG.md +++ /dev/null @@ -1,16 +0,0 @@ -# @jspsych-timelines/spatial-cueing - -## 0.2.0 - -### Minor Changes - -- 22c0236: delete -task suffix - -## 0.1.0 - -### Minor Changes - -- d6a0e3b: upgrade jspsych depedencies to v8 -- 0de5056: update all jspsych dependencies to v8 -- abfa815: add spatial cueing task package -- 0b18198: upgrade to use jspsych v8 diff --git a/packages/spatial-cueing/README.md b/packages/spatial-cueing/README.md deleted file mode 100644 index 4e97e752..00000000 --- a/packages/spatial-cueing/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# spatial-cueing - -## Overview - -A shareable timeline of the Posner spatial cueing task. - -## Loading - -### In browser - -```html - - - - - - - - - - diff --git a/packages/spatial-cueing/jest.config.js b/packages/spatial-cueing/jest.config.js deleted file mode 100644 index bd1aacef..00000000 --- a/packages/spatial-cueing/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/spatial-cueing/package.json b/packages/spatial-cueing/package.json deleted file mode 100644 index 35490e62..00000000 --- a/packages/spatial-cueing/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@jspsych-timelines/spatial-cueing", - "version": "0.2.0", - "description": "A shareable timeline of the Posner spatial cueing task.", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.global.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpatialCueingTask" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/jspsych/jspsych-timelines.git" - }, - "keywords": [ - "jsPsych" - ], - "author": { - "name": "Cherrie Chang", - "url": "https://github.com/cchang-vassar" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/jspsych/jspsych-timelines/issues" - }, - "homepage": "https://github.com/jspsych/jspsych-timelines/packages/spatial-cueing#readme", - "peerDependencies": { - "jspsych": "^8.0.1" - }, - "dependencies": {}, - "devDependencies": { - "tsup": "^8.2.4", - "typescript": "^5.5.4" - } -} \ No newline at end of file diff --git a/packages/spatial-cueing/src/index.ts b/packages/spatial-cueing/src/index.ts deleted file mode 100644 index e979524f..00000000 --- a/packages/spatial-cueing/src/index.ts +++ /dev/null @@ -1,377 +0,0 @@ -import { JsPsych } from "jspsych"; -import jsPsychHtmlKeyboardResponse from '@jspsych/plugin-html-keyboard-response' - -/* internal constants */ -const KEYPRESS_TO_NEXT_SLIDE: String = 'Press any key to move to the next slide.'; - -enum StimulusBox { - Default = `

    `, - Highlighted = `

    `, - WithStimulus = `

    X

    `, - WithStimulusHighlighted = `

    X

    ` -}; - -enum FixationBox { - NoCue = `

     

    +

     

    `, - LeftCue = `

    +

     

    `, - RightCue = `

    +

     

    `, - BiCue = `

    ↔︎

    +

     

    `, -}; - -enum Direction { - Left = -1, - Right = 1, - Bi = 2, - None = 0 -}; - -enum Validity { - Valid = 1, - Invalid = -1, - Neutral = 0, - None = -2, -}; - -const ALL_TEST_COMBOS = [ - { - validity: Validity.Valid, - stimulus_direction: Direction.Left, - }, - { - validity: Validity.Valid, - stimulus_direction: Direction.Right, - }, - { - validity: Validity.Invalid, - stimulus_direction: Direction.Left, - }, - { - validity: Validity.Invalid, - stimulus_direction: Direction.Right, - }, - { - validity: Validity.Neutral, - stimulus_direction: Direction.Left, - }, - { - validity: Validity.Neutral, - stimulus_direction: Direction.Right, - } -]; - -// function that generate start instructions -function showStartInstruction(endogenous_cue: boolean, blank_period: number, cue_period: number) { - const start_instruction = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - return ( - `

    This is a demo of the Posner spatial cueing task

    ` + - `

    In each trial, you will see a fixation cross in the center and two empty boxes on the left and right of the cross.\n` + - (endogenous_cue - ? `After ` + - blank_period + - `seconds, you will see an arrow pointing left, right or both directions appear on top of the fixation cross.\n` - : `After ` + - blank_period + - `seconds, you will see either the left, right or both boxes darken slightly.\n`) + - `Then after ` + - cue_period + - `seconds, you will see a black 'X' appear in either one of the boxes.\n` + - (endogenous_cue - ? `Most of the time, the location of the 'X' matches the direction the arrow is pointing at.\n` + - `However, sometimes they do not match. For example, sometimes the arrow points to both directions,` + - `but the 'X' can only be in one box. Another example would be if the arrow points left, but the 'X' is in the right box.\n` - : `Most of the time, the 'X' appears in the darkened box.\n` + - `However, sometimes it may appear in the other, non-darkened box.`) + - `Your task is to respond as quickly as possible when you see the 'X'. If it appears in the left box, press 'f' on your keyboard. If it appears in the right box, press 'j'.

    ` + - KEYPRESS_TO_NEXT_SLIDE + `
    ` - ); - }, - }; - - return start_instruction; -}; - -// function that generate end instruction -function showEndInstruction() { - const end_instruction = { - type: jsPsychHtmlKeyboardResponse, - stimulus: `
    -

    That is the end of the demo.

    -

    Thank you for participating!

    - `, - }; - - return end_instruction; -} - -// function that generate blank template -function makeDefaultTemplate(blank_period: number) { - const stimulus = `
    ` + StimulusBox.Default + FixationBox.NoCue + StimulusBox.Default + '
    '; - const default_template = { - type: jsPsychHtmlKeyboardResponse, - stimulus: stimulus, - trial_duration: blank_period, - choices: "NO_KEYS" - }; - - return default_template; -}; - -// function that generate endogenous cue -function makeEndogenousCue(jsPsych: JsPsych, cue_period: number) { - const endogenous_cue = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - const validity = jsPsych.evaluateTimelineVariable("validity"); - const stimulus_direction = jsPsych.evaluateTimelineVariable("stimulus_direction"); - const cue_direction = cueDirectionMapper(validity, stimulus_direction); - - var stimulus = `
    ` + StimulusBox.Default; - switch (cue_direction) { - case Direction.Left: - stimulus += FixationBox.LeftCue; - break; - case Direction.Right: - stimulus += FixationBox.RightCue; - break; - case Direction.Bi: - stimulus += FixationBox.BiCue; - break; - default: - stimulus += FixationBox.NoCue; - }; - stimulus += StimulusBox.Default + `
    `; - - return stimulus; - }, - trial_duration: cue_period, - choices: "NO_KEYS" - }; - - return endogenous_cue; -}; - -// function that generate exogenous cue -function makeExogenousCue(jsPsych: JsPsych, cue_period: number) { - const exogenous_cue = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - const validity = jsPsych.evaluateTimelineVariable("validity"); - const stimulus_direction = jsPsych.evaluateTimelineVariable("stimulus_direction"); - const cue_direction = cueDirectionMapper(validity, stimulus_direction); - - var stimulus = `
    ` - + (cue_direction == Direction.Left || cue_direction == Direction.Bi ? StimulusBox.Highlighted : StimulusBox.Default) - + FixationBox.NoCue - + (cue_direction == Direction.Right || cue_direction == Direction.Bi ? StimulusBox.Highlighted : StimulusBox.Default) - + `
    `; - - return stimulus; - }, - trial_duration: cue_period, - choices: "NO_KEYS" - }; - - return exogenous_cue; -}; - -// function that call the cue maker based on endogenous vs exogenous -function makeCue(jsPsych: JsPsych, endogenous_cue: boolean, cue_period: number) { - return endogenous_cue ? makeEndogenousCue(jsPsych, cue_period) : makeExogenousCue(jsPsych, cue_period); -}; - -// function that generate exogenous stimulus -function makeExogenousStimulus(jsPsych: JsPsych) { - const exogenous_stimulus = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - const validity = jsPsych.evaluateTimelineVariable("validity"); - const stimulus_direction = jsPsych.evaluateTimelineVariable("stimulus_direction"); - const cue_direction = cueDirectionMapper(validity, stimulus_direction); - - var stimulus = `
    `; - if (cue_direction == Direction.Left || cue_direction == Direction.Bi) { - stimulus += jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Left ? StimulusBox.WithStimulusHighlighted : StimulusBox.Highlighted; - } - else if (jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Left) { - stimulus += StimulusBox.WithStimulus; - } - else { - stimulus += StimulusBox.Default; - } - - stimulus += FixationBox.NoCue; - - if (cue_direction == Direction.Right || cue_direction == Direction.Bi) { - stimulus += jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Right ? StimulusBox.WithStimulusHighlighted : StimulusBox.Highlighted; - } - else if (jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Right) { - stimulus += StimulusBox.WithStimulus; - } - else { - stimulus += StimulusBox.Default; - } - - stimulus += `
    `; - - return stimulus; - }, - choices: ['f', 'j'], - data: { - task: "stimulus", - cue_type: "exogenous", - correct_response: () => { - return jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Left ? 'f' : 'j'; - } - }, - on_finish: function (data: any) { - data.correct = jsPsych.pluginAPI.compareKeys( - data.response, - data.correct_response - ); - } - }; - - return exogenous_stimulus; -}; - -// function that generate endogenous stimulus -function makeEndogenousStimulus(jsPsych: JsPsych) { - const endogenous_stimulus = { - type: jsPsychHtmlKeyboardResponse, - stimulus: () => { - const validity = jsPsych.evaluateTimelineVariable("validity"); - const stimulus_direction = jsPsych.evaluateTimelineVariable("stimulus_direction"); - const cue_direction = cueDirectionMapper(validity, stimulus_direction); - - var stimulus = `
    ` + (jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Left ? StimulusBox.WithStimulus : StimulusBox.Default); - switch (cue_direction) { - case Direction.Left: - stimulus += FixationBox.LeftCue; - break; - case Direction.Right: - stimulus += FixationBox.RightCue; - break; - case Direction.Bi: - stimulus += FixationBox.BiCue; - break; - default: - stimulus += FixationBox.NoCue; - break; - }; - - stimulus += (jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Right ? StimulusBox.WithStimulus : StimulusBox.Default) + `
    `; - - return stimulus; - }, - choices: ['f', 'j'], - data: { - task: "stimulus", - cue_type: "endogenous", - correct_response: () => { - return jsPsych.evaluateTimelineVariable("stimulus_direction") == Direction.Left ? 'f' : 'j'; - } - }, - on_finish: function (data: any) { - data.correct = jsPsych.pluginAPI.compareKeys( - data.response, - data.correct_response - ); - } - }; - - return endogenous_stimulus; -}; - -// function to map stimulus direction to cue direction based on cue validity -function cueDirectionMapper(validity: Validity, stimulus_direction: Direction) { - switch (validity) { - case Validity.Valid: - return stimulus_direction; - case Validity.Invalid: - return stimulus_direction * -1; - case Validity.Neutral: - return Direction.Bi; - case Validity.None: - return Direction.None; - default: - return Direction.None; - }; -}; - -// function that call the stimulus maker based on endogenous vs exogenous -function makeStimulus(jsPsych: JsPsych, endogenous_cue: boolean) { - return endogenous_cue ? makeEndogenousStimulus(jsPsych) : makeExogenousStimulus(jsPsych); -}; - - -// make a trial consisting of blank, cue and stimulus -function createTrialTimeline(jsPsych: JsPsych, endogenous_cue: boolean, blank_period: number, cue_period: number) { - const single_trial_timeline = { - timeline: [ - makeDefaultTemplate(blank_period), - makeCue(jsPsych, endogenous_cue, cue_period), - makeStimulus(jsPsych, endogenous_cue) - ] - }; - - return single_trial_timeline; -}; - -// generate a Posner spatial cueing task timeline -export function createTimeline( - jsPsych: JsPsych, { - endogenous_cue = false, - blank_period = 2000, - cue_period = 2000, - num_trials = 60, // must be divisible by 6 - valid_proportion = 0.8 - }: { - endogenous_cue?: boolean, - blank_period?: number, - cue_period?: number, - num_trials?: number, - valid_proportion?: number - } = {}) - { - jsPsych = jsPsych; - const num_each_valid_trial_type = num_trials * valid_proportion / 2; - const num_each_invalid_trial_type = (num_trials - (num_each_valid_trial_type * 2)) / 4; - var weights: number[] = [ - num_each_valid_trial_type, num_each_valid_trial_type, - num_each_invalid_trial_type, num_each_invalid_trial_type, - num_each_invalid_trial_type, num_each_invalid_trial_type - ]; - - const spatial_cueing_timeline = { - timeline: [ - createTrialTimeline(jsPsych, endogenous_cue, blank_period, cue_period), - ], - timeline_variables: ALL_TEST_COMBOS, - sample: { - type: "with-replacement", - size: num_trials, - weights: weights - } - }; - - return spatial_cueing_timeline; -}; - -export const timelineUnits = { - createTrialTimeline -}; - -export const utils = { - showStartInstruction, - showEndInstruction, - makeDefaultTemplate, - cueDirectionMapper, - makeEndogenousCue, - makeExogenousCue, - makeCue, - makeExogenousStimulus, - makeEndogenousStimulus, - makeStimulus -} \ No newline at end of file diff --git a/packages/spatial-cueing/src/styles.css b/packages/spatial-cueing/src/styles.css deleted file mode 100644 index 97040407..00000000 --- a/packages/spatial-cueing/src/styles.css +++ /dev/null @@ -1,35 +0,0 @@ -.jspsych-spatial-cueing-instruction { - max-width: 50vw; -} - -.jspsych-spatial-cueing-container { - width: 30vw; - height: 10vh; - display: flex; - flex-direction: row; - justify-content: space-evenly; - align-items: center; -} - -.jspsych-spatial-cueing-target-container, .jspsych-spatial-cueing-target-container-bold, .jspsych-spatial-cueing-target-container-stimulus { - width: 48px; - height: 48px; - border: 1px solid black; - justify-content: center; - align-items: center; - line-height: 0px; - font-size: 24px; -} - -.jspsych-spatial-cueing-target-container-bold { - background-color: #BEBEBE; -} - -.jspsych-spatial-cueing-fixation-container { - width: 48px; - height: fit-content; - justify-content: center; - align-items: center; - line-height: 0px; - font-size: 24px; -} \ No newline at end of file diff --git a/packages/spatial-cueing/tsconfig.json b/packages/spatial-cueing/tsconfig.json deleted file mode 100644 index 33655f70..00000000 --- a/packages/spatial-cueing/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@jspsych/config/tsconfig.contrib.json", - "compilerOptions": { - "baseUrl": "." - }, - "include": [ - "src", - "../cli/src/build.js" - ] -} \ No newline at end of file From ac91c93c36e31761e011fc821b6209098ef500e8 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 8 Jul 2025 11:36:39 -0400 Subject: [PATCH 27/37] keeping only the respective package --- packages/flanker-inhibitory/README.md | 128 + .../docs/flanker-inhibitory.md | 242 + .../flanker-inhibitory/examples/index.html | 111 + .../jest.config.cjs | 0 packages/flanker-inhibitory/package.json | 40 + packages/flanker-inhibitory/src/index.spec.ts | 750 ++ packages/flanker-inhibitory/src/index.ts | 768 ++ packages/flanker-inhibitory/src/stimuli.ts | 52 + packages/flanker-inhibitory/src/styles.css | 416 + packages/flanker-inhibitory/src/text.ts | 152 + .../tsconfig.json | 0 packages/spatial-nback/README.md | 36 - packages/spatial-nback/docs/spatial-nback.md | 98 - packages/spatial-nback/examples/index.html | 62 - packages/spatial-nback/package-lock.json | 10419 ---------------- packages/spatial-nback/package.json | 40 - packages/spatial-nback/src/index.spec.ts | 186 - packages/spatial-nback/src/index.ts | 283 - packages/spatial-nback/src/text.ts | 38 - packages/speeded-matching/README.md | 103 - .../speeded-matching/docs/speeded-matching.md | 100 - packages/speeded-matching/examples/index.html | 42 - packages/speeded-matching/jest.config.cjs | 1 - packages/speeded-matching/package.json | 36 - packages/speeded-matching/src/index copy.ts | 528 - packages/speeded-matching/src/index.spec.ts | 683 - packages/speeded-matching/src/index.ts | 527 - packages/speeded-matching/src/styles.css | 266 - packages/speeded-matching/src/test-items.ts | 116 - packages/speeded-matching/src/text.ts | 80 - packages/speeded-matching/tsconfig.json | 10 - 31 files changed, 2659 insertions(+), 13654 deletions(-) create mode 100644 packages/flanker-inhibitory/README.md create mode 100644 packages/flanker-inhibitory/docs/flanker-inhibitory.md create mode 100644 packages/flanker-inhibitory/examples/index.html rename packages/{spatial-nback => flanker-inhibitory}/jest.config.cjs (100%) create mode 100644 packages/flanker-inhibitory/package.json create mode 100644 packages/flanker-inhibitory/src/index.spec.ts create mode 100644 packages/flanker-inhibitory/src/index.ts create mode 100644 packages/flanker-inhibitory/src/stimuli.ts create mode 100644 packages/flanker-inhibitory/src/styles.css create mode 100644 packages/flanker-inhibitory/src/text.ts rename packages/{spatial-nback => flanker-inhibitory}/tsconfig.json (100%) delete mode 100644 packages/spatial-nback/README.md delete mode 100644 packages/spatial-nback/docs/spatial-nback.md delete mode 100644 packages/spatial-nback/examples/index.html delete mode 100644 packages/spatial-nback/package-lock.json delete mode 100644 packages/spatial-nback/package.json delete mode 100644 packages/spatial-nback/src/index.spec.ts delete mode 100644 packages/spatial-nback/src/index.ts delete mode 100644 packages/spatial-nback/src/text.ts delete mode 100644 packages/speeded-matching/README.md delete mode 100644 packages/speeded-matching/docs/speeded-matching.md delete mode 100644 packages/speeded-matching/examples/index.html delete mode 100644 packages/speeded-matching/jest.config.cjs delete mode 100644 packages/speeded-matching/package.json delete mode 100644 packages/speeded-matching/src/index copy.ts delete mode 100644 packages/speeded-matching/src/index.spec.ts delete mode 100644 packages/speeded-matching/src/index.ts delete mode 100644 packages/speeded-matching/src/styles.css delete mode 100644 packages/speeded-matching/src/test-items.ts delete mode 100644 packages/speeded-matching/src/text.ts delete mode 100644 packages/speeded-matching/tsconfig.json diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md new file mode 100644 index 00000000..e28b96f6 --- /dev/null +++ b/packages/flanker-inhibitory/README.md @@ -0,0 +1,128 @@ +# flanker-inhibitory + +## Overview + +The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and selective attention based on the NIH Toolbox Flanker task. Participants respond to the direction of a central stimulus while ignoring flanking stimuli that may be congruent or incongruent with the target. + +## Loading + +### In browser + +```html + +``` + +### Via NPM + +```bash +npm install @jspsych-timelines/flanker-inhibitory +``` + +```js +import { createTimeline, timelineUnits, utils } from "@jspsych-timelines/flanker-inhibitory" +``` + +## Compatibility + +`@jspsych-timelines/flanker-inhibitory` requires jsPsych v8.0.0 or later. + +## Documentation + +### createTimeline + +#### jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { *options* }) ⇒ timeline + +Creates a complete flanker task timeline with instructions, practice trials, and main task trials. The task uses layered fish and arrow stimuli by default, with configurable parameters for customization. + +The following parameters can be specified in the **options** parameter. + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| stimuli_type | string | 'layered' | Type of stimuli to use: 'fish', 'arrow', 'layered', or 'custom' | +| svg | array | undefined | Override parameter - array of SVGs to layer (should be right-facing; left version auto-generated) | +| stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd ≥3) | +| fixation_duration | number | 500 | Duration of fixation cross in milliseconds | +| show_instructions | boolean | true | Whether to show instruction screens | +| show_practice | boolean | true | Whether to include practice trials | +| num_practice | number | 8 | Number of practice trials | +| num_trials | number | 20 | Number of main task trials | +| enable_tts | boolean | false | Enable text-to-speech functionality | +| tts_voice | string | '' | Voice name for TTS (empty uses default) | +| tts_rate | number | 1.0 | Speech rate (0.1 to 10) | +| tts_pitch | number | 1.0 | Speech pitch (0 to 2) | +| tts_volume | number | 1.0 | Speech volume (0 to 1) | +| tts_lang | string | 'en-US' | Language code for TTS | +| speak_instructions | boolean | true | Speak instruction page content | +| speak_prompts | boolean | true | Speak trial prompts | +| speak_feedback | boolean | true | Speak feedback messages | +| auto_speak | boolean | true | Auto-start speaking when content loads | + +#### Example Usage + +```js +const jsPsych = initJsPsych(); + +// Basic usage with defaults +const timeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych); + +// Custom configuration +const customTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + stimuli_type: 'fish', + stimuli_amount: 7, + num_trials: 40, + fixation_duration: 750 +}); + +// Using custom SVG override (provide right-facing SVGs) +const svgTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + svg: [ + '...right-facing base layer...', + '...right-facing overlay layer...' + ], + stimuli_amount: 9 + // Left-facing versions will be automatically generated by flipping +}); + +// With TTS enabled +const ttsTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + enable_tts: true, + speak_instructions: true, + speak_prompts: true, + speak_feedback: true, + tts_rate: 0.9, + tts_lang: 'en-US' +}); + +jsPsych.run([timeline]); +``` + +### timelineUnits + +Object containing identifiers for different phases of the task: +- `instructions`: Instruction screens +- `practice`: Practice trials +- `main`: Main task trials +- `completion`: Task completion screen + +### utils + +Utility functions for working with flanker stimuli and data: +- `calculatePerformance(data)`: Calculate accuracy and mean RT from trial data +- `createFlankerStim(direction, congruent, stimuli, stimuli_amount)`: Generate flanker stimulus HTML +- `createPracticeStim(direction, congruent, stimuli, stimuli_amount)`: Generate practice stimulus with highlighting + +## Features + +- **Adaptive stimuli**: Fish stimuli for younger children, arrows for older children, or layered combinations +- **Configurable difficulty**: Adjust number of flanker stimuli (3, 5, 7, 9, etc.) +- **Mobile-friendly**: Touch-optimized button interfaces +- **Practice mode**: Highlighted center stimulus for learning +- **Multiple input types**: Support for SVG arrays, custom stimuli objects, or simple overrides +- **Automatic flipping**: Single right-facing SVG automatically generates left-facing version +- **Layered SVGs**: Stack multiple SVG elements for complex stimuli +- **Text-to-Speech**: Optional TTS support for accessibility and younger participants +- **Speech controls**: Space bar to skip speech, configurable voice settings + +## Author / Citation + +Hunter Farhat \ No newline at end of file diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md new file mode 100644 index 00000000..0d9a97e0 --- /dev/null +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -0,0 +1,242 @@ +# flanker-inhibitory + +The Flanker Inhibitory Control and Attention test is an assessment of inhibitory control and selective attention. Based on the NIH Toolbox Flanker task, participants must respond to the direction of a central stimulus while ignoring flanking stimuli that may be congruent (pointing the same direction) or incongruent (pointing the opposite direction). + +## Parameters + +### Configuration Parameters + +Configuration parameters can be passed when calling `createTimeline()`: + +```js +const timeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { + stimuli_type: 'fish', + stimuli_amount: 7, + num_trials: 40 +}); +``` + +Parameter | Type | Default Value | Description +----------|------|---------------|------------ +stimuli_type | string | 'layered' | Type of stimuli: 'fish', 'arrow', 'layered' (fish + arrow), or 'custom' +svg | array | undefined | Override parameter - array of right-facing SVG strings to layer (left version auto-generated) +stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd number ≥3) +fixation_duration | number | 500 | Duration of fixation cross display in milliseconds +show_instructions | boolean | true | Whether to display instruction screens before the task +show_practice | boolean | true | Whether to include practice trials with feedback +num_practice | number | 8 | Number of practice trials to present +num_trials | number | 20 | Number of main task trials to present +enable_tts | boolean | false | Enable text-to-speech functionality +tts_voice | string | '' | Voice name for TTS (empty string uses default system voice) +tts_rate | number | 1.0 | Speech rate (0.1 to 10, where 1.0 is normal speed) +tts_pitch | number | 1.0 | Speech pitch (0 to 2, where 1.0 is normal pitch) +tts_volume | number | 1.0 | Speech volume (0 to 1, where 1.0 is maximum volume) +tts_lang | string | 'en-US' | Language code for speech synthesis +speak_instructions | boolean | true | Whether to speak instruction page content aloud +speak_prompts | boolean | true | Whether to speak trial prompts aloud +speak_feedback | boolean | true | Whether to speak feedback messages aloud +auto_speak | boolean | true | Whether to automatically start speaking when content loads + +### Stimulus Configuration Examples + +```js +// Use fish stimuli only +const fishConfig = { stimuli_type: 'fish' }; + +// Use custom right-facing SVG with 7 total stimuli +const customConfig = { + svg: ['...right-facing custom svg...'], + stimuli_amount: 7 + // Left-facing version automatically generated +}; + +// Use layered stimuli with custom amounts +const layeredConfig = { + stimuli_type: 'layered', + stimuli_amount: 9 +}; + +// Use custom stimuli type with predefined custom_stimulus +const customTypeConfig = { + stimuli_type: 'custom', + stimuli_amount: 7 +}; + +// Enable TTS with custom voice settings +const ttsConfig = { + enable_tts: true, + speak_instructions: true, + speak_prompts: true, + speak_feedback: true, + tts_rate: 0.8, + tts_pitch: 1.1, + tts_lang: 'en-US' +}; +``` + +### Timeline Structure + +The complete timeline includes: + +1. **Instructions** (if `show_instructions: true`) + - Multi-page instructions explaining the task + - Examples of congruent and incongruent trials + - Strategy tips for performance + +2. **Practice Trials** (if `show_practice: true`) + - Highlighted center stimulus to aid learning + - Immediate feedback after each response + - Performance summary at completion + +3. **Main Task** + - Equal numbers of congruent and incongruent trials + - Left and right direction trials + - Randomized trial order + +4. **Completion Screen** + - Final performance summary + - Task completion confirmation + +## Data Generated + +Each trial generates the following data: + +Name | Type | Value +-----|------|------ +task | string | 'flanker' for stimulus trials, 'fixation' for fixation periods +phase | string | 'practice' or 'main' depending on trial phase +direction | string | 'left' or 'right' - direction of center stimulus +congruent | boolean | true if flankers match center direction, false if opposite +response | number | 0 for left button, 1 for right button +rt | number | Response time in milliseconds +correct | boolean | true if response matches stimulus direction +trial_number | number | Sequential trial number within phase + +### Performance Calculation + +The `calculatePerformance()` utility function returns: + +```js +{ + accuracy: 85.5, // Percentage of correct responses + mean_rt: 650, // Mean response time for correct trials (ms) + total_trials: 20, // Total number of trials analyzed + correct_trials: 17 // Number of correct responses +} +``` + +## Functions + +### createTimeline(jsPsych, config) + +Creates the complete flanker task timeline with the specified configuration. + +**Parameters:** +- `jsPsych`: jsPsych instance +- `config`: Configuration object (see Parameters section) + +**Returns:** Timeline object ready for use with `jsPsych.run()` + +### calculatePerformance(data) + +Analyzes trial data to compute performance metrics. + +**Parameters:** +- `data`: Array of trial data objects + +**Returns:** Performance object with accuracy, mean RT, and trial counts + +### createFlankerStim(direction, congruent, stimuli, stimuli_amount) + +Generates HTML for flanker stimulus display. + +**Parameters:** +- `direction`: 'left' or 'right' +- `congruent`: boolean indicating trial type +- `stimuli`: Stimuli object or array +- `stimuli_amount`: Number of stimuli to display (default: 5) + +**Returns:** HTML string for stimulus display + +### createPracticeStim(direction, congruent, stimuli, stimuli_amount) + +Generates HTML for practice stimulus with center highlighting. + +**Parameters:** Same as `createFlankerStim()` + +**Returns:** HTML string with highlighted center stimulus + +## Stimulus System + +### Layered SVGs + +The package supports layering multiple SVG elements to create complex stimuli: + +```js +const layeredStimuli = [ + '...base fish shape...', + '...arrow overlay...', + '...additional elements...' +]; +``` + +### Automatic Flipping + +Single SVGs are automatically flipped to create left/right facing versions: + +```js +// Provide only right-facing SVG +const config = { + svg: ['...right-facing fish...'] +}; +// Left-facing version is automatically generated +``` + +### Mobile Optimization + +- Touch-friendly button interfaces +- Responsive sizing using vmin units +- Optimized for various screen sizes +- No keyboard input required + +### Text-to-Speech (TTS) Support + +The package includes comprehensive TTS functionality for accessibility: + +#### Features +- **Configurable voice settings**: Rate, pitch, volume, and language +- **Selective content**: Choose what content to speak (instructions, prompts, feedback) +- **Speech controls**: Space bar to skip current speech +- **Auto-play options**: Automatically start speaking when content loads +- **Browser compatibility**: Uses Web Speech API (available in most modern browsers) + +#### TTS Configuration +```js +const config = { + enable_tts: true, + tts_voice: 'Google US English', // Specific voice name (optional) + tts_rate: 0.9, // Slightly slower than normal + tts_pitch: 1.0, // Normal pitch + tts_volume: 0.8, // 80% volume + tts_lang: 'en-US', // Language code + speak_instructions: true, + speak_prompts: true, + speak_feedback: true, + auto_speak: true +}; +``` + +#### Speech Controls +- **Space bar**: Skip current speech +- **Automatic cleanup**: Speech stops when task completes +- **Error handling**: Graceful fallback if TTS not supported + +#### Browser Support +TTS requires the Web Speech API, supported in: +- Chrome/Chromium browsers +- Safari (desktop and mobile) +- Edge +- Firefox (limited support) + +If TTS is not supported, the task continues normally without speech. + diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html new file mode 100644 index 00000000..980fbd15 --- /dev/null +++ b/packages/flanker-inhibitory/examples/index.html @@ -0,0 +1,111 @@ + + + + + Flanker Inhibitory Control Task Example + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/spatial-nback/jest.config.cjs b/packages/flanker-inhibitory/jest.config.cjs similarity index 100% rename from packages/spatial-nback/jest.config.cjs rename to packages/flanker-inhibitory/jest.config.cjs diff --git a/packages/flanker-inhibitory/package.json b/packages/flanker-inhibitory/package.json new file mode 100644 index 00000000..b032ee44 --- /dev/null +++ b/packages/flanker-inhibitory/package.json @@ -0,0 +1,40 @@ +{ + "name": "@jspsych-timelines/flanker-inhibitory", + "version": "0.0.1", + "description": "Flanker Inhibitory Control and Attention test timeline for jsPsych with configurable fish/arrow stimuli and layered SVG support", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "unpkg": "dist/index.global.js", + "scripts": { + "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineFlankerInhibitory" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/jspsych/jspsych-timelines.git", + "directory": "packages/flanker-inhibitory" + }, + "keywords": [ + "jsPsych", + "flanker", + "inhibitory control", + "attention", + "NIH Toolbox", + "cognitive assessment" + ], + "author": { + "name": "Hunter Farhat", + "url": "https://github.com/farhat60" + }, + "license": "MIT", + "bugs": { + "url": "https://github.com/jspsych/jspsych-timelines/issues" + }, + "homepage": "https://github.com/jspsych/jspsych-timelines/tree/main/packages/flanker-inhibitory#readme", + "devDependencies": { + "jspsych": "^8.0.0", + "@jspsych/config": "^3.2.2", + "tsup": "^6.7.0", + "typescript": "^5.0.2" + } + } \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts new file mode 100644 index 00000000..1e158a38 --- /dev/null +++ b/packages/flanker-inhibitory/src/index.spec.ts @@ -0,0 +1,750 @@ +import { initJsPsych } from "jspsych"; +import { + createTimeline, + createFlankerStim, + createPracticeStim, + calculatePerformance, + default_stimuli, + fish_stimuli, + arrow_stimuli, + timelineUnits, + utils, + trial_text, + instruction_pages, + FlankerConfig +} from "./index"; +import { layered_stimuli } from "./stimuli"; + +describe("Flanker Inhibitory Control Task", () => { + let jsPsych: any; + + beforeEach(() => { + jsPsych = initJsPsych(); + }); + + // Helper function to count only top-level spans (flanker positions) + const countFlankerPositions = (html: string): number => { + // Extract the content inside the flanker-stim div + const flankerMatch = html.match(/
    ]*>([\s\S]*?)<\/div>/); + if (!flankerMatch) return 0; + + const content = flankerMatch[1]; + + // Count only direct child spans - these are the stimulus positions + // Look for spans that are not nested inside other spans + let count = 0; + let depth = 0; + let i = 0; + + while (i < content.length) { + if (content.substring(i).startsWith('', i) + 1; + } else if (content.substring(i).startsWith('')) { + depth--; + i = content.indexOf('>', i) + 1; + } else { + i++; + } + } + + return count; + }; + + describe("createTimeline", () => { + it("should return a timeline object with timeline array", () => { + const timeline = createTimeline(jsPsych); + expect(timeline).toBeDefined(); + expect(timeline.timeline).toBeDefined(); + expect(Array.isArray(timeline.timeline)).toBe(true); + }); + + it("should create timeline with default parameters", () => { + const timeline = createTimeline(jsPsych); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should create timeline without instructions when disabled", () => { + const timeline = createTimeline(jsPsych, { show_instructions: false }); + expect(timeline.timeline.length).toBeGreaterThan(0); + // Should have fewer trials than with instructions + const timelineWithInstructions = createTimeline(jsPsych, { show_instructions: true }); + expect(timeline.timeline.length).toBeLessThan(timelineWithInstructions.timeline.length); + }); + + it("should create timeline without practice when disabled", () => { + const timeline = createTimeline(jsPsych, { show_practice: false }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should respect custom trial count", () => { + const customTrials = 15; + const timeline = createTimeline(jsPsych, { + num_trials: customTrials, + show_instructions: false, + show_practice: false + }); + + // Should have main trials plus completion screen + expect(timeline.timeline.length).toBeGreaterThanOrEqual(2); + }); + + it("should respect custom practice trial count", () => { + const customPractice = 6; + const timeline = createTimeline(jsPsych, { + num_practice: customPractice, + show_practice: true, + show_instructions: false + }); + + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should handle different stimuli types", () => { + const fishTimeline = createTimeline(jsPsych, { stimuli_type: 'fish' }); + const arrowTimeline = createTimeline(jsPsych, { stimuli_type: 'arrow' }); + const layeredTimeline = createTimeline(jsPsych, { stimuli_type: 'layered' }); + const customTimeline = createTimeline(jsPsych, { stimuli_type: 'custom' }); + + expect(fishTimeline.timeline.length).toBeGreaterThan(0); + expect(arrowTimeline.timeline.length).toBeGreaterThan(0); + expect(layeredTimeline.timeline.length).toBeGreaterThan(0); + expect(customTimeline.timeline.length).toBeGreaterThan(0); + }); + + it("should handle SVG override parameter", () => { + const customSVGs = [ + '', + '' + ]; + + const timeline = createTimeline(jsPsych, { svg: customSVGs }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + + it("should set custom fixation duration", () => { + const customDuration = 1000; + const timeline = createTimeline(jsPsych, { fixation_duration: customDuration }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + }); + + describe("createFlankerStim", () => { + it("should create HTML for congruent left stimulus", () => { + const html = createFlankerStim('left', true); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should create HTML for incongruent right stimulus", () => { + const html = createFlankerStim('right', false); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should create HTML for congruent right stimulus", () => { + const html = createFlankerStim('right', true); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should create HTML for incongruent left stimulus", () => { + const html = createFlankerStim('left', false); + expect(html).toContain('flanker-stim'); + expect(html).toContain(''); + }); + + it("should handle custom stimuli arrays", () => { + const customStimuli = ['test']; + const html = createFlankerStim('left', true, customStimuli); + expect(html).toContain('flanker-stim'); + }); + + it("should handle custom stimuli objects", () => { + const customStimuli = { + left: ['left test'], + right: ['right test'] + }; + const html = createFlankerStim('left', true, customStimuli); + expect(html).toContain('flanker-stim'); + }); + }); + + describe("createPracticeStim", () => { + it("should create HTML for practice stimulus with highlighting", () => { + const html = createPracticeStim('left', true); + expect(html).toContain('flanker-stim practice'); + expect(html).toContain('center highlighted'); + }); + + it("should create different HTML than regular stimulus", () => { + const practiceHtml = createPracticeStim('left', true); + const regularHtml = createFlankerStim('left', true); + + expect(practiceHtml).toContain('practice'); + expect(practiceHtml).toContain('highlighted'); + expect(regularHtml).not.toContain('practice'); + expect(regularHtml).not.toContain('highlighted'); + }); + + it("should handle all direction and congruency combinations", () => { + const combinations = [ + ['left', true], + ['left', false], + ['right', true], + ['right', false] + ]; + + combinations.forEach(([direction, congruent]) => { + const html = createPracticeStim(direction as string, congruent as boolean); + expect(html).toContain('flanker-stim practice'); + expect(html).toContain('center highlighted'); + }); + }); + }); + + describe("calculatePerformance", () => { + it("should return zero performance for empty data", () => { + const performance = calculatePerformance([]); + expect(performance.accuracy).toBe(0); + expect(performance.mean_rt).toBe(0); + expect(performance.total_trials).toBe(0); + }); + + it("should calculate accuracy correctly", () => { + const mockData = [ + { task: 'flanker', correct: true, rt: 500 }, + { task: 'flanker', correct: false, rt: 600 }, + { task: 'flanker', correct: true, rt: 550 }, + { task: 'flanker', correct: true, rt: 450 } + ]; + + const performance = calculatePerformance(mockData); + expect(performance.accuracy).toBe(75); // 3/4 = 75% + expect(performance.total_trials).toBe(4); + expect(performance.correct_trials).toBe(3); + expect(performance.mean_rt).toBeCloseTo(500, 1); // Mean of correct trials: (500+550+450)/3 + }); + + it("should filter only flanker task data", () => { + const mockData = [ + { task: 'flanker', correct: true, rt: 500 }, + { task: 'other', correct: false, rt: 600 }, + { task: 'flanker', correct: true, rt: 550 } + ]; + + const performance = calculatePerformance(mockData); + expect(performance.total_trials).toBe(2); // Only flanker trials + expect(performance.accuracy).toBe(100); // Both flanker trials correct + }); + + it("should handle all incorrect trials", () => { + const mockData = [ + { task: 'flanker', correct: false, rt: 500 }, + { task: 'flanker', correct: false, rt: 600 } + ]; + + const performance = calculatePerformance(mockData); + expect(performance.accuracy).toBe(0); + expect(performance.mean_rt).toBe(0); // No correct trials + expect(performance.correct_trials).toBe(0); + }); + }); + + describe("Stimulus Constants", () => { + it("should provide default_stimuli with left and right", () => { + expect(default_stimuli).toBeDefined(); + expect(default_stimuli.left).toBeDefined(); + expect(default_stimuli.right).toBeDefined(); + expect(typeof default_stimuli.left).toBe('string'); + expect(typeof default_stimuli.right).toBe('string'); + }); + + it("should provide fish_stimuli with left and right", () => { + expect(fish_stimuli).toBeDefined(); + expect(fish_stimuli.left).toBeDefined(); + expect(fish_stimuli.right).toBeDefined(); + expect(typeof fish_stimuli.left).toBe('string'); + expect(typeof fish_stimuli.right).toBe('string'); + }); + + it("should provide arrow_stimuli with left and right", () => { + expect(arrow_stimuli).toBeDefined(); + expect(arrow_stimuli.left).toBeDefined(); + expect(arrow_stimuli.right).toBeDefined(); + expect(typeof arrow_stimuli.left).toBe('string'); + expect(typeof arrow_stimuli.right).toBe('string'); + }); + + it("should have different stimuli for left and right directions", () => { + expect(default_stimuli.left).not.toBe(default_stimuli.right); + expect(fish_stimuli.left).not.toBe(fish_stimuli.right); + expect(arrow_stimuli.left).not.toBe(arrow_stimuli.right); + }); + }); + + describe("Text and Instructions", () => { + it("should provide trial_text object", () => { + expect(trial_text).toBeDefined(); + expect(trial_text.continue_button).toBeDefined(); + expect(trial_text.left_button).toBeDefined(); + expect(trial_text.right_button).toBeDefined(); + expect(trial_text.fixation_cross).toBeDefined(); + expect(trial_text.correct_feedback).toBeDefined(); + expect(trial_text.incorrect_feedback).toBeDefined(); + }); + + it("should provide instruction_pages array", () => { + expect(instruction_pages).toBeDefined(); + expect(Array.isArray(instruction_pages)).toBe(true); + expect(instruction_pages.length).toBeGreaterThan(0); + }); + + it("should have properly structured instruction pages", () => { + instruction_pages.forEach((page, index) => { + expect(page).toBeDefined(); + expect(page.buttons).toBeDefined(); + expect(Array.isArray(page.buttons)).toBe(true); + }); + }); + }); + + describe("Timeline Units", () => { + it("should provide timelineUnits object", () => { + expect(timelineUnits).toBeDefined(); + expect(timelineUnits.instructions).toBeDefined(); + expect(timelineUnits.practice).toBeDefined(); + expect(timelineUnits.main).toBeDefined(); + expect(timelineUnits.completion).toBeDefined(); + }); + }); + + describe("Utils Export", () => { + it("should provide utils object with functions", () => { + expect(utils).toBeDefined(); + expect(utils.calculatePerformance).toBeDefined(); + expect(utils.createFlankerStim).toBeDefined(); + expect(utils.createPracticeStim).toBeDefined(); + expect(typeof utils.calculatePerformance).toBe('function'); + expect(typeof utils.createFlankerStim).toBe('function'); + expect(typeof utils.createPracticeStim).toBe('function'); + }); + + it("should have utils functions work the same as direct exports", () => { + const mockData = [{ task: 'flanker', correct: true, rt: 500 }]; + + const directResult = calculatePerformance(mockData); + const utilsResult = utils.calculatePerformance(mockData); + + expect(directResult).toEqual(utilsResult); + }); + }); + + describe("FlankerConfig Interface", () => { + it("should accept valid config objects", () => { + const configs: FlankerConfig[] = [ + {}, + { stimuli_type: 'fish' }, + { stimuli_type: 'arrow' }, + { stimuli_type: 'layered' }, + { fixation_duration: 1000 }, + { show_instructions: false }, + { show_practice: true }, + { num_practice: 10 }, + { num_trials: 25 }, + { svg: ['test'] } + ]; + + configs.forEach(config => { + expect(() => createTimeline(jsPsych, config)).not.toThrow(); + }); + }); + }); + + describe("SVG Flipping Functionality", () => { + it("should generate different left and right stimuli from same input", () => { + // Test with a simple SVG array input + const testSVG = ['']; + + const leftHtml = createFlankerStim('left', true, testSVG); + const rightHtml = createFlankerStim('right', true, testSVG); + + // Should be different due to flipping + expect(leftHtml).not.toBe(rightHtml); + expect(leftHtml).toContain('flanker-stim'); + expect(rightHtml).toContain('flanker-stim'); + }); + + it("should handle SVGs with existing transforms", () => { + // Test with SVG that already has transform (like the fish) + const transformedSVG = ['']; + + const leftHtml = createFlankerStim('left', true, transformedSVG); + const rightHtml = createFlankerStim('right', true, transformedSVG); + + expect(leftHtml).not.toBe(rightHtml); + expect(leftHtml).toContain('flanker-stim'); + expect(rightHtml).toContain('flanker-stim'); + }); + + it("should properly handle fish SVG alignment - no double transforms", () => { + // Test specifically with fish stimuli to ensure alignment fix works + const fishLeftHtml = createFlankerStim('left', true, fish_stimuli); + const fishRightHtml = createFlankerStim('right', true, fish_stimuli); + + // Both should contain flanker stimulus + expect(fishLeftHtml).toContain('flanker-stim'); + expect(fishRightHtml).toContain('flanker-stim'); + + // They should be different (one flipped) + expect(fishLeftHtml).not.toBe(fishRightHtml); + + // Test that the fish SVG original has a transform + expect(fish_stimuli.right).toContain('transform='); + + // Test that flipping twice gets back to original orientation + // This validates the fix for double-transform issues + const twice_flipped_left = createFlankerStim('left', true, fish_stimuli); + const twice_flipped_right = createFlankerStim('right', true, fish_stimuli); + expect(twice_flipped_left).not.toBe(twice_flipped_right); + }); + + it("should handle fish incongruent stimuli alignment correctly", () => { + // Test incongruent fish (center different from flankers) - key alignment test + const leftIncongruent = createFlankerStim('left', false, fish_stimuli); + const rightIncongruent = createFlankerStim('right', false, fish_stimuli); + + // Should produce valid HTML + expect(leftIncongruent).toContain('flanker-stim'); + expect(rightIncongruent).toContain('flanker-stim'); + + // Should be different + expect(leftIncongruent).not.toBe(rightIncongruent); + + // The key test: each should contain both orientations (center vs flankers) + // This is where alignment issues would be most visible + expect(leftIncongruent).toContain(''); + expect(rightIncongruent).toContain(''); + }); + + it("should maintain consistent alignment with layered fish+arrow stimuli", () => { + // Test the default layered stimuli (fish + arrow) for alignment + const leftLayered = createFlankerStim('left', true, default_stimuli); + const rightLayered = createFlankerStim('right', true, default_stimuli); + + expect(leftLayered).toContain('flanker-stim'); + expect(rightLayered).toContain('flanker-stim'); + expect(leftLayered).not.toBe(rightLayered); + + // Should contain position styling for layered elements + expect(leftLayered).toContain('position:'); + expect(rightLayered).toContain('position:'); + }); + }); + + describe("Layered SVG Functionality", () => { + it("should handle multiple SVG layers", () => { + const layeredSVGs = [ + '', + '' + ]; + + const html = createFlankerStim('left', true, layeredSVGs); + + expect(html).toContain('flanker-stim'); + expect(html).toContain('position: relative'); + }); + + it("should handle single SVG in array", () => { + const singleSVG = ['']; + + const html = createFlankerStim('left', true, singleSVG); + + expect(html).toContain('flanker-stim'); + }); + }); + + describe("Stimuli Amount Functionality", () => { + + it("should default to 5 stimuli (2 left + 1 center + 2 right)", () => { + const html = createFlankerStim('left', true); + const spanCount = countFlankerPositions(html); + expect(spanCount).toBe(5); + }); + + it("should create correct number of stimuli for custom amounts", () => { + const testAmounts = [3, 5, 7, 9]; + + testAmounts.forEach(amount => { + const html = createFlankerStim('left', true, layered_stimuli, amount); + const spanCount = countFlankerPositions(html); + expect(spanCount).toBe(amount); + }); + }); + + it("should handle even numbers by rounding up to next odd", () => { + const html4 = createFlankerStim('left', true, layered_stimuli, 4); + const html6 = createFlankerStim('left', true, layered_stimuli, 6); + const html8 = createFlankerStim('left', true, layered_stimuli, 8); + + expect(countFlankerPositions(html4)).toBe(5); // 4 -> 5 + expect(countFlankerPositions(html6)).toBe(7); // 6 -> 7 + expect(countFlankerPositions(html8)).toBe(9); // 8 -> 9 + }); + + it("should enforce minimum of 3 stimuli", () => { + const html1 = createFlankerStim('left', true, layered_stimuli, 1); + const html2 = createFlankerStim('left', true, layered_stimuli, 2); + const htmlNegative = createFlankerStim('left', true, layered_stimuli, -5); + + expect(countFlankerPositions(html1)).toBe(3); + expect(countFlankerPositions(html2)).toBe(3); + expect(countFlankerPositions(htmlNegative)).toBe(3); + }); + + it("should maintain center stimulus with highlighting in practice", () => { + const html = createPracticeStim('left', true, layered_stimuli, 7); + const spanCount = countFlankerPositions(html); + expect(spanCount).toBe(7); + expect(html).toContain('center highlighted'); + }); + + it("should work with different stimuli types", () => { + const fishHtml = createFlankerStim('left', true, fish_stimuli, 7); + const arrowHtml = createFlankerStim('left', true, arrow_stimuli, 7); + + expect(countFlankerPositions(fishHtml)).toBe(7); + expect(countFlankerPositions(arrowHtml)).toBe(7); + }); + + it("should be configurable via timeline config", () => { + const timeline = createTimeline(jsPsych, { + stimuli_amount: 7, + show_instructions: false, + show_practice: false + }); + + expect(timeline.timeline.length).toBeGreaterThan(0); + }); + + it("should validate stimuli_amount in different function calls", () => { + // Test with simple SVGs (no inner spans) + const testSVGs = ['']; + const html1 = createFlankerStim('left', true, testSVGs, 9); + expect(countFlankerPositions(html1)).toBe(9); + + // Test with objects + const testObj = { left: [''], right: [''] }; + const html2 = createFlankerStim('left', true, testObj, 7); + expect(countFlankerPositions(html2)).toBe(7); + }); + + it("should handle invalid amounts by using valid fallbacks", () => { + // NaN should fall back to default (5) + const htmlNaN = createFlankerStim('left', true, layered_stimuli, NaN); + expect(countFlankerPositions(htmlNaN)).toBe(5); + + // Very large numbers should still work + const htmlLarge = createFlankerStim('left', true, layered_stimuli, 15); + expect(countFlankerPositions(htmlLarge)).toBe(15); + }); + }); + + describe("Error Handling", () => { + it("should handle invalid direction gracefully", () => { + expect(() => createFlankerStim('invalid' as any, true)).not.toThrow(); + }); + + it("should handle undefined stimuli", () => { + expect(() => createFlankerStim('left', true, undefined as any)).not.toThrow(); + }); + + it("should handle empty stimuli array", () => { + expect(() => createFlankerStim('left', true, [])).not.toThrow(); + }); + + it("should handle malformed SVG strings", () => { + const badSVG = ['not an svg']; + expect(() => createFlankerStim('left', true, badSVG)).not.toThrow(); + }); + + it("should handle invalid stimuli_amount gracefully", () => { + expect(() => createFlankerStim('left', true, layered_stimuli, NaN)).not.toThrow(); + expect(() => createFlankerStim('left', true, layered_stimuli, Infinity)).not.toThrow(); + + // Should fall back to default value (5) for invalid inputs + const htmlNaN = createFlankerStim('left', true, layered_stimuli, NaN); + const htmlInfinity = createFlankerStim('left', true, layered_stimuli, Infinity); + + expect(countFlankerPositions(htmlNaN)).toBe(5); + expect(countFlankerPositions(htmlInfinity)).toBe(5); + }); + }); + + describe("Edge Cases and Timeline Variable Errors", () => { + it("should handle num_practice=0 with show_practice=true without errors", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: 0, + show_practice: true, + show_instructions: false, + num_trials: 1 + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle num_trials=0 without errors", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_trials: 0, + show_instructions: false, + show_practice: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle both num_practice=0 and num_trials=0 without errors", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: 0, + show_practice: true, + num_trials: 0, + show_instructions: false + }); + // Should still have completion screen + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle negative numbers for practice trials", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: -5, + show_practice: true, + show_instructions: false, + num_trials: 1 + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle negative numbers for main trials", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_trials: -10, + show_instructions: false, + show_practice: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle very large numbers without breaking", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: 1000, + show_practice: true, + num_trials: 1000, + show_instructions: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle NaN values for trial counts", () => { + expect(() => { + const timeline = createTimeline(jsPsych, { + num_practice: NaN, + show_practice: true, + num_trials: NaN, + show_instructions: false + }); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + + it("should handle all combinations of false/0 settings", () => { + const edgeCaseConfigs = [ + { show_instructions: false, show_practice: false, num_practice: 0, num_trials: 0 }, + { show_instructions: true, show_practice: false, num_practice: 0, num_trials: 0 }, + { show_instructions: false, show_practice: true, num_practice: 0, num_trials: 0 }, + { show_instructions: false, show_practice: false, num_practice: 5, num_trials: 0 }, + { show_instructions: false, show_practice: false, num_practice: 0, num_trials: 5 }, + ]; + + edgeCaseConfigs.forEach((config, index) => { + expect(() => { + const timeline = createTimeline(jsPsych, config); + expect(timeline.timeline.length).toBeGreaterThan(0); + }).not.toThrow(); + }); + }); + + it("should always include completion screen even with no trials", () => { + const timeline = createTimeline(jsPsych, { + show_instructions: false, + show_practice: false, + num_practice: 0, + num_trials: 0 + }); + + // Should have at least the main intro and completion screens + expect(timeline.timeline.length).toBeGreaterThanOrEqual(2); + + // Check that completion screen is present + const completionScreen = timeline.timeline.find(trial => + trial.data && trial.data.task === 'complete' + ); + expect(completionScreen).toBeDefined(); + }); + }); + + describe("Responsive Sizing", () => { + it("should add CSS custom property for stimuli count", () => { + const html3 = createFlankerStim('left', true, fish_stimuli, 3); + const html7 = createFlankerStim('left', true, fish_stimuli, 7); + const html15 = createFlankerStim('left', true, fish_stimuli, 15); + + expect(html3).toContain('--stimuli-count: 3'); + expect(html7).toContain('--stimuli-count: 7'); + expect(html15).toContain('--stimuli-count: 15'); + }); + + it("should add CSS custom property to practice stimuli", () => { + const practiceHtml = createPracticeStim('right', false, arrow_stimuli, 9); + + expect(practiceHtml).toContain('--stimuli-count: 9'); + expect(practiceHtml).toContain('practice'); + }); + + it("should handle different stimuli counts with custom property", () => { + const testCounts = [3, 5, 7, 9, 11, 15, 21]; + + testCounts.forEach(count => { + const html = createFlankerStim('left', true, default_stimuli, count); + expect(html).toContain(`--stimuli-count: ${count}`); + expect(html).toContain('flanker-stim'); + }); + }); + + it("should work with edge cases for responsive sizing", () => { + // Test with very large count + const htmlLarge = createFlankerStim('left', true, fish_stimuli, 25); + expect(htmlLarge).toContain('--stimuli-count: 25'); + + // Test with minimum count (enforced to 3) + const htmlMin = createFlankerStim('left', true, fish_stimuli, 1); + expect(htmlMin).toContain('--stimuli-count: 3'); + + // Test with invalid input (should fall back to default) + const htmlInvalid = createFlankerStim('left', true, fish_stimuli, NaN); + expect(htmlInvalid).toContain('--stimuli-count: 5'); + }); + }); +}); \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts new file mode 100644 index 00000000..91185ff0 --- /dev/null +++ b/packages/flanker-inhibitory/src/index.ts @@ -0,0 +1,768 @@ +import { JsPsych } from "jspsych"; +import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; +import { trial_text, instruction_pages, tts_config, tts_text } from './text'; +import { layered_stimuli, fish_only, arrow_only, custom_stimulus } from './stimuli'; + +// ============================================================================ +// TTS (TEXT-TO-SPEECH) UTILITIES +// ============================================================================ + +// Global TTS state management +let currentUtterance: SpeechSynthesisUtterance | null = null; +let speechQueue: string[] = []; +let speechEnabled = false; + +// Initialize TTS functionality +export function initializeTTS(config = tts_config) { + speechEnabled = 'speechSynthesis' in window; + + if (!speechEnabled) { + console.warn('Text-to-Speech not supported in this browser'); + return false; + } + + // Stop any existing speech + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + + return true; +} + +// Get available voices +export function getAvailableVoices(): SpeechSynthesisVoice[] { + return speechSynthesis.getVoices(); +} + +// Create speech utterance with configuration +export function createUtterance(text: string, config = tts_config): SpeechSynthesisUtterance { + const utterance = new SpeechSynthesisUtterance(text); + + // Apply voice settings + utterance.rate = config.rate; + utterance.pitch = config.pitch; + utterance.volume = config.volume; + utterance.lang = config.lang; + + // Set voice if specified + if (config.voice_name) { + const voices = getAvailableVoices(); + const selectedVoice = voices.find(voice => + voice.name.includes(config.voice_name) || voice.lang === config.lang + ); + if (selectedVoice) { + utterance.voice = selectedVoice; + } + } + + return utterance; +} + +// Stop current speech +export function stopSpeech() { + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + currentUtterance = null; + speechQueue = []; +} + +// Speak text with delay and configuration +export function speakText(text: string, config = tts_config, delay = config.speech_delay): Promise { + return new Promise((resolve) => { + if (!speechEnabled || !text.trim()) { + resolve(); + return; + } + + setTimeout(() => { + // Stop any current speech + if (speechSynthesis.speaking) { + speechSynthesis.cancel(); + } + + currentUtterance = createUtterance(text, config); + + currentUtterance.onend = () => { + currentUtterance = null; + resolve(); + }; + + currentUtterance.onerror = () => { + currentUtterance = null; + resolve(); + }; + + speechSynthesis.speak(currentUtterance); + }, delay); + }); +} + +// Speak instruction page content +export function speakInstructionPage(pageContent: string, config = tts_config): Promise { + if (!config.speak_instructions) { + return Promise.resolve(); + } + + // Remove HTML tags and clean up text for speech + const cleanText = pageContent + .replace(/<[^>]*>/g, ' ') // Remove HTML tags + .replace(/\s+/g, ' ') // Normalize any sequence of whitespace (including those left by the previous replace) to a single space + .trim(); + + return speakText(cleanText, config); +} + +// Speak trial prompt +export function speakTrialPrompt(config = tts_config): Promise { + if (!config.speak_prompts) { + return Promise.resolve(); + } + + return speakText(tts_text.trial_prompt_spoken, config); +} + +// Speak feedback +export function speakFeedback(isCorrect: boolean, config = tts_config): Promise { + if (!config.speak_feedback) { + return Promise.resolve(); + } + + const feedbackText = isCorrect ? tts_text.correct_spoken : tts_text.incorrect_spoken; + return speakText(feedbackText, config); +} + +// Add keyboard listener for speech control +export function addSpeechControls(config = tts_config) { + if (!config.allow_skip) return undefined; + + const handleKeyPress = (event: KeyboardEvent) => { + if (event.code === 'Space' && speechSynthesis.speaking) { + event.preventDefault(); + stopSpeech(); + } + }; + + document.addEventListener('keydown', handleKeyPress); + + // Return cleanup function + return () => { + document.removeEventListener('keydown', handleKeyPress); + }; +} + +// ============================================================================ +// SVG UTILITIES +// ============================================================================ + +// Robust SVG flipping with proper alignment handling +function flipSVG(svgString: string): string { + if (!svgString || typeof svgString !== 'string') return svgString; + + // Check if SVG already contains a flip transform + const hasExistingTransform = svgString.includes('matrix(-1,') || + svgString.includes('matrix(-1 ') || + svgString.includes('scaleX(-1)') || + svgString.includes('scale(-1'); + + // If already flipped, remove the flip transform to restore original orientation + if (hasExistingTransform) { + return svgString + .replace(/transform="matrix\(-1,\s*0,\s*0,\s*1,\s*0,\s*0\)"/g, '') + .replace(/transform="matrix\(-1\s+0\s+0\s+1\s+0\s+0\)"/g, '') + .replace(/transform="scaleX\(-1\)"/g, '') + .replace(/transform="scale\(-1,\s*1\)"/g, '') + .replace(/transform="scale\(-1\s+1\)"/g, '') + .replace(/style="[^"]*transform:\s*scaleX\(-1\)[^"]*"/g, (match) => { + return match.replace(/transform:\s*scaleX\(-1\);?/, '').replace(/style=""/, ''); + }); + } + + // Parse SVG to get dimensions for proper transform calculation + const viewBoxMatch = svgString.match(/viewBox="([^"]+)"/); + const widthMatch = svgString.match(/width="([^"]+)"/); + + let translateX = 0; + if (viewBoxMatch) { + const viewBox = viewBoxMatch[1].split(/\s+/); + if (viewBox.length >= 3) { + translateX = parseFloat(viewBox[2]) || 0; // width from viewBox + } + } else if (widthMatch) { + translateX = parseFloat(widthMatch[1]) || 0; + } + + // Apply proper SVG transform that maintains alignment + if (translateX > 0) { + // Insert transform into the SVG element itself for better precision + return svgString.replace( + /]*)>/, + `` + ).replace(/<\/svg>$/, ''); + } else { + // Fallback to CSS transform with inline-block container + return `${svgString}`; + } +} + +// Simple SVG layering using CSS +function layerSVGs(svgArray: string[], flip: boolean = false): string { + if (!svgArray || !Array.isArray(svgArray) || svgArray.length === 0) return ''; + if (svgArray.length === 1) return flip ? flipSVG(svgArray[0]) : svgArray[0]; + + // Layer SVGs using CSS positioning, flip each SVG individually if needed + return ` + ${svgArray.map((svg, index) => + `${flip ? flipSVG(svg) : svg}` + ).join('')} + `; +} + +// Simplified stimulus processing +function processStimuli(stimuli: { left?: string[], right?: string[] } | string[] | { left: string, right: string }) { + // If already processed (has left/right strings), return as-is + if (stimuli && typeof stimuli === 'object' && !Array.isArray(stimuli) && + typeof stimuli.left === 'string' && typeof stimuli.right === 'string') { + return stimuli as { left: string, right: string }; + } + + // If simple array, treat as right-facing and generate left by flipping + if (Array.isArray(stimuli)) { + return { + right: layerSVGs(stimuli, false), + left: layerSVGs(stimuli, true) + }; + } + + // If object with arrays + if (stimuli && typeof stimuli === 'object') { + const obj = stimuli as { left?: string[], right?: string[] }; + if (obj.left && obj.right) { + return { + left: layerSVGs(obj.left, false), + right: layerSVGs(obj.right, false) + }; + } else if (obj.right) { + return { + right: layerSVGs(obj.right, false), + left: layerSVGs(obj.right, true) + }; + } else if (obj.left) { + return { + left: layerSVGs(obj.left, false), + right: layerSVGs(obj.left, true) + }; + } + } + + // Default fallback + return { + right: layerSVGs(layered_stimuli, false), + left: layerSVGs(layered_stimuli, true) + }; +} + +// Pre-processed stimuli for convenience (lazy-loaded) +export const default_stimuli = processStimuli(layered_stimuli); +export const fish_stimuli = processStimuli(fish_only); +export const arrow_stimuli = processStimuli(arrow_only); + +// ============================================================================ +// STIMULUS CREATION FUNCTIONS +// ============================================================================ + +export function createFlankerStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { + // Ensure stimuli_amount is valid (odd number ≥3) + let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; + const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); + const halfFlankers = Math.floor(validAmount / 2); + + // Process stimuli + const processedStimuli = processStimuli(stimuli); + const center = processedStimuli[direction]; + const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; + + let html = `
    `; + + // Add left flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + // Add center stimulus + html += `${center}`; + + // Add right flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + html += `
    `; + return html; +} + +export function createPracticeStim(direction, congruent, stimuli: string[] | { left?: string[]; right?: string[] } | { left: string; right: string } = layered_stimuli, stimuli_amount: number = 5) { + // Ensure stimuli_amount is valid (odd number ≥3) + let safeAmount = typeof stimuli_amount === 'number' && !isNaN(stimuli_amount) && isFinite(stimuli_amount) ? stimuli_amount : 5; + const validAmount = Math.max(3, safeAmount % 2 === 0 ? safeAmount + 1 : safeAmount); + const halfFlankers = Math.floor(validAmount / 2); + + // Process stimuli + const processedStimuli = processStimuli(stimuli); + const center = processedStimuli[direction]; + const flanker = congruent ? processedStimuli[direction] : processedStimuli[direction === 'left' ? 'right' : 'left']; + + let html = `
    `; + + // Add left flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + // Add center stimulus with highlighting + html += `${center}`; + + // Add right flankers + for (let i = 0; i < halfFlankers; i++) { + html += `${flanker}`; + } + + html += `
    `; + return html; +} + +// ============================================================================ +// INTERFACES +// ============================================================================ + +export interface FlankerConfig { + stimuli_type?: 'fish' | 'arrow' | 'layered' | 'custom'; + svg?: string[]; // Override parameter - array of SVGs to layer + stimuli_amount?: number; // Number of stimuli in flanker display (default: 5, must be odd ≥3) + fixation_duration?: number; + show_instructions?: boolean; + show_practice?: boolean; + num_practice?: number; + num_trials?: number; + + // TTS Configuration + enable_tts?: boolean; // Enable text-to-speech functionality + tts_voice?: string; // Voice name to use for TTS + tts_rate?: number; // Speech rate (0.1 to 10) + tts_pitch?: number; // Speech pitch (0 to 2) + tts_volume?: number; // Speech volume (0 to 1) + tts_lang?: string; // Language code for TTS + speak_instructions?: boolean; // Speak instruction pages + speak_prompts?: boolean; // Speak trial prompts + speak_feedback?: boolean; // Speak feedback messages + auto_speak?: boolean; // Automatically start speaking when content loads +} + +// ============================================================================ +// UTILITY FUNCTIONS +// ============================================================================ + +export function calculatePerformance(data: any[]) { + const trial_data = data.filter(d => d.task === 'flanker'); + + if (trial_data.length === 0) { + return { accuracy: 0, mean_rt: 0, total_trials: 0 }; + } + + const correct_trials = trial_data.filter(d => d.correct); + const accuracy = (correct_trials.length / trial_data.length) * 100; + const mean_rt = correct_trials.length > 0 + ? correct_trials.reduce((sum, d) => sum + d.rt, 0) / correct_trials.length + : 0; + + return { + accuracy, + mean_rt, + total_trials: trial_data.length, + correct_trials: correct_trials.length + }; +} + +// ============================================================================ +// MAIN TIMELINE FUNCTION +// ============================================================================ + +export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { + const { + stimuli_type = 'layered', + svg, + stimuli_amount = 5, + fixation_duration = 500, + show_instructions = true, + show_practice = true, + num_practice = 8, + num_trials = 20, + + // TTS configuration + enable_tts = false, + tts_voice = tts_config.voice_name, + tts_rate = tts_config.rate, + tts_pitch = tts_config.pitch, + tts_volume = tts_config.volume, + tts_lang = tts_config.lang, + speak_instructions = tts_config.speak_instructions, + speak_prompts = tts_config.speak_prompts, + speak_feedback = tts_config.speak_feedback, + auto_speak = tts_config.auto_speak_on_load, + } = config; + + // Initialize TTS if enabled + let ttsSettings = { ...tts_config }; + let speechCleanup: (() => void) | undefined; + + if (enable_tts) { + ttsSettings = { + ...tts_config, + voice_name: tts_voice, + rate: tts_rate, + pitch: tts_pitch, + volume: tts_volume, + lang: tts_lang, + speak_instructions, + speak_prompts, + speak_feedback, + auto_speak_on_load: auto_speak, + }; + + const ttsInitialized = initializeTTS(ttsSettings); + if (ttsInitialized) { + speechCleanup = addSpeechControls(ttsSettings); + } + } + + // Determine which stimuli to use based on priority: + // 1. svg override parameter (highest priority) + // 2. stimuli_type selection + let stimuli; + if (svg) { + stimuli = processStimuli(svg); // Process the SVG override array to handle flipping + } else if (stimuli_type === 'custom') { + stimuli = processStimuli(custom_stimulus); + } else if (stimuli_type === 'arrow') { + stimuli = arrow_stimuli; + } else if (stimuli_type === 'fish') { + stimuli = fish_stimuli; + } else { + stimuli = default_stimuli; // layered by default + } + const timeline = []; + + // Instructions + if (show_instructions) { + instruction_pages.forEach((page, index) => { + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
    + ${page.header ? `

    ${page.header}

    ` : ''} + ${page.header2 ? `

    ${page.header2}

    ` : ''} + ${page.description ? `

    ${page.description}

    ` : ''} + ${page.task_explanation ? `

    ${page.task_explanation}

    ` : ''} + ${page.performance_note ? `

    ${page.performance_note}

    ` : ''} + ${page.strategy_title ? `

    ${page.strategy_title}

    ` : ''} + ${page.strategy_intro ? `

    ${page.strategy_intro}

    ` : ''} + ${page.strategy_points ? ` +
      + ${page.strategy_points.map(point => `
    • ${point}
    • `).join('')} +
    + ` : ''} + ${page.start_prompt ? `

    ${page.start_prompt}

    ` : ''} + ${enable_tts && ttsSettings.allow_skip ? `

    ${tts_text.speech_controls_note}

    ` : ''} +
    + `, + choices: page.buttons || [trial_text.continue_button], + data: { task: 'instructions', page: index + 1 }, + on_load: enable_tts ? function() { + if (index === 0) { + // Speak welcome message for first page + speakText(tts_text.welcome_spoken, ttsSettings); + } else if (index === 1) { + // Speak task explanation for second page + speakText(tts_text.task_explanation_spoken, ttsSettings); + } else if (index === 2) { + // Speak directions for third page + speakText(tts_text.directions_spoken, ttsSettings); + } + } : undefined + }); + }); + } + + // Practice + if (show_practice) { + // Practice intro + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
    +

    ${trial_text.practice_header}

    +

    ${trial_text.practice_intro_message}

    + ${enable_tts && ttsSettings.allow_skip ? `

    ${tts_text.speech_controls_note}

    ` : ''} +
    + `, + choices: [trial_text.start_button], + data: { task: 'practice-intro' }, + on_load: enable_tts ? function() { + speakText(tts_text.practice_start_spoken, ttsSettings); + } : undefined + }); + + // Only add practice trials if num_practice > 0 + if (num_practice > 0) { + // Practice trials + const practice_variables = [ + {direction: 'left', congruent: true}, + {direction: 'left', congruent: false}, + {direction: 'right', congruent: true}, + {direction: 'right', congruent: false}, + ]; + + const practice_trials = jsPsych.randomization.repeat(practice_variables, Math.floor(num_practice/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(practice_variables, num_practice%4)); + + // Fixation + const practice_fixation = { + type: jsPsychHtmlButtonResponse, + stimulus: `
    ${trial_text.fixation_cross}
    `, + choices: [], + trial_duration: fixation_duration, + data: { task: 'fixation', phase: 'practice' } + }; + + // Practice trial + const practice_trial = { + type: jsPsychHtmlButtonResponse, + stimulus: function() { + const direction = jsPsych.evaluateTimelineVariable('direction'); + const congruent = jsPsych.evaluateTimelineVariable('congruent'); + return ` +
    +
    ${trial_text.main_task_prompt}
    + ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} +
    + `; + }, + choices: [trial_text.left_button, trial_text.right_button], + data: { + task: 'flanker', + phase: 'practice', + direction: jsPsych.timelineVariable('direction'), + congruent: jsPsych.timelineVariable('congruent') + }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_prompts) { + speakTrialPrompt(ttsSettings); + } + } : undefined, + on_finish: function(data: any) { + const correct_response = data.direction === 'left' ? 0 : 1; + data.correct = data.response === correct_response; + } + }; + + // Feedback + const practice_feedback = { + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const last_trial = jsPsych.data.get().last(1).values()[0]; + const feedback = last_trial.correct ? trial_text.correct_feedback : trial_text.incorrect_feedback; + return ``; + }, + choices: [], + trial_duration: 1000, + data: { task: 'feedback', phase: 'practice' }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_feedback) { + const last_trial = jsPsych.data.get().last(1).values()[0]; + speakFeedback(last_trial.correct, ttsSettings); + } + } : undefined + }; + + // Practice timeline + timeline.push({ + timeline: [practice_fixation, practice_trial, practice_feedback], + timeline_variables: practice_trials, + randomize_order: true + }); + } + + // Practice completion + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); + const performance = calculatePerformance(practice_data); + + return ` +
    +

    ${trial_text.practice_complete_header}

    +

    ${trial_text.practice_complete_message}

    +

    ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

    + ${enable_tts && ttsSettings.allow_skip ? `

    ${tts_text.speech_controls_note}

    ` : ''} +
    + `; + }, + choices: [trial_text.continue_button], + data: { task: 'practice-complete' }, + on_load: enable_tts ? function() { + speakText(tts_text.practice_complete_spoken, ttsSettings); + } : undefined + }); + } + + // Main task + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: ` +
    +

    ${trial_text.main_task_header}

    +

    ${trial_text.main_task_intro}

    + ${enable_tts && ttsSettings.allow_skip ? `

    ${tts_text.speech_controls_note}

    ` : ''} +
    + `, + choices: [trial_text.ready_button], + data: { task: 'main-intro' }, + on_load: enable_tts ? function() { + speakText(tts_text.main_task_start_spoken, ttsSettings); + } : undefined + }); + + // Main trials - only add if num_trials > 0 + if (num_trials > 0) { + const trial_variables = [ + {direction: 'left', congruent: true}, + {direction: 'left', congruent: false}, + {direction: 'right', congruent: true}, + {direction: 'right', congruent: false}, + ]; + + const main_trials = jsPsych.randomization.repeat(trial_variables, Math.floor(num_trials/4)) + .concat(jsPsych.randomization.sampleWithoutReplacement(trial_variables, num_trials%4)); + + // Fixation + const fixation = { + type: jsPsychHtmlButtonResponse, + stimulus: `
    ${trial_text.fixation_cross}
    `, + choices: [], + trial_duration: fixation_duration, + data: { task: 'fixation', phase: 'main' } + }; + + // Main trial + const flanker_trial = { + type: jsPsychHtmlButtonResponse, + stimulus: function() { + const direction = jsPsych.evaluateTimelineVariable('direction'); + const congruent = jsPsych.evaluateTimelineVariable('congruent'); + return ` +
    +
    ${trial_text.main_task_prompt}
    + ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)} +
    + `; + }, + choices: [trial_text.left_button, trial_text.right_button], + data: { + task: 'flanker', + phase: 'main', + direction: jsPsych.timelineVariable('direction'), + congruent: jsPsych.timelineVariable('congruent') + }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_prompts) { + speakTrialPrompt(ttsSettings); + } + } : undefined, + on_finish: function(data: any) { + const correct_response = data.direction === 'left' ? 0 : 1; + data.correct = data.response === correct_response; + } + }; + + // Main task timeline + timeline.push({ + timeline: [fixation, flanker_trial], + timeline_variables: main_trials, + randomize_order: true + }); + } + + // Completion + timeline.push({ + type: jsPsychHtmlButtonResponse, + stimulus: () => { + const main_data = jsPsych.data.get().filter({task: 'flanker', phase: 'main'}).values(); + const performance = calculatePerformance(main_data); + + return ` +
    +

    ${trial_text.task_complete_header}

    +

    ${trial_text.task_complete_message}

    +
    +

    ${trial_text.performance_title}

    +

    ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

    +

    ${trial_text.response_time_label} ${performance.mean_rt.toFixed(0)}ms

    +
    + ${enable_tts && ttsSettings.allow_skip ? `

    ${tts_text.speech_controls_note}

    ` : ''} +
    + `; + }, + choices: [trial_text.end_button], + data: { task: 'complete' }, + on_load: enable_tts ? function() { + if (ttsSettings.speak_completion) { + speakText(tts_text.task_complete_spoken, ttsSettings); + } + } : undefined, + on_finish: function() { + // Cleanup speech controls when task is complete + if (speechCleanup) { + speechCleanup(); + } + stopSpeech(); + } + }); + + return { + timeline: timeline + }; +} + +// ============================================================================ +// EXPORTS +// ============================================================================ + +export const timelineUnits = { + instructions: "Instructions for the flanker inhibitory control task", + practice: "Practice trials with feedback", + main: "Main flanker task trials", + completion: "Task completion screen" +}; + +export const utils = { + calculatePerformance, + createFlankerStim, + createPracticeStim, + + // TTS utilities + initializeTTS, + speakText, + stopSpeech, + getAvailableVoices, + speakInstructionPage, + speakTrialPrompt, + speakFeedback, + addSpeechControls +}; + +// Export text for external use +export { trial_text, instruction_pages, tts_config, tts_text } from './text'; + +// Default export +export default { createTimeline, timelineUnits, utils }; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/stimuli.ts b/packages/flanker-inhibitory/src/stimuli.ts new file mode 100644 index 00000000..fed70288 --- /dev/null +++ b/packages/flanker-inhibitory/src/stimuli.ts @@ -0,0 +1,52 @@ +// ============================================================================ +// LAYERED SVG STIMULI +// ============================================================================ + +// Fish SVG (base layer) +const fish_svg = ` + + + + + + + + +`; + +// Arrow SVG (overlay layer) +const arrow_svg = ` + +`; + +// Default layered stimuli - fish with arrow overlay +export const layered_stimuli = [ + fish_svg, + arrow_svg +]; + +// Fish only +export const fish_only = [ + fish_svg +]; + +// Arrow only +export const arrow_only = [ + arrow_svg +]; + +// Custom stimulus - you can add more layers +export const custom_stimulus = { + left: [ + '', + '' + + '' + + '' + ], + right: [ + '', + '' + + '' + + '' + ] +}; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css new file mode 100644 index 00000000..3ee61d23 --- /dev/null +++ b/packages/flanker-inhibitory/src/styles.css @@ -0,0 +1,416 @@ +/* Flanker Inhibitory Control Task Styles - Simple jsPsych approach */ + +/* Instructions */ +.flanker-instructions { + max-width: 90vw; + max-height: 85vh; + margin: 0 auto; + padding: 3vmin; + text-align: center; + line-height: 1.4; + background-color: white; + border-radius: 2vmin; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); + overflow-y: auto; +} + +.flanker-instructions h1 { + color: #2c3e50; + text-align: center; + margin-bottom: 2vmin; + font-size: 4.5vmin; +} + +.flanker-instructions h2 { + color: #34495e; + margin-top: 3vmin; + margin-bottom: 1.5vmin; + font-size: 3.5vmin; + text-align: center; +} + +.flanker-instructions p { + color: #555; + margin-bottom: 1.5vmin; + font-size: 3vmin; + text-align: center; +} + +.flanker-instructions .performance-note { + background-color: #e8f4fd; + border-left: 4px solid #3498db; + padding: 1.5vmin 2vmin; + margin: 2vmin 0; + font-weight: 500; + border-radius: 1vmin; +} + +.flanker-instructions .start-prompt { + text-align: center; + font-weight: 600; + color: #2c3e50; + margin-top: 2vmin; + font-size: 3.5vmin; +} + +.flanker-instructions ul { + padding-left: 3vmin; + margin: 2vmin 0; +} + +.flanker-instructions li { + margin: 1vmin 0; + font-size: 3vmin; + color: #555; + text-align: center; +} + +/* Performance summary */ +.performance-summary { + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 2vmin; + padding: 4vmin; + margin: 4vmin 0; + text-align: center; +} + +.performance-summary h3 { + color: #495057; + margin-bottom: 3vmin; + text-align: center; + font-size: 4.5vmin; +} + +.performance-summary p { + margin: 2vmin 0; + font-size: 3.5vmin; + color: #6c757d; + text-align: center; +} + +/* Trial display */ +.flanker-trial { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 50vh; + padding: 3vmin; + gap: 4vmin; +} + +.trial-prompt { + font-size: 3vmin; + font-weight: 200; + color: #2c3e50; + text-align: center; + max-width: 90vw; + line-height: normal; +} + +/* Flanker stimulus */ +.flanker-stim { + display: flex; + align-items: center; + justify-content: center; + margin: 3vmin 0; + padding: 3vmin; + width: 100%; + max-width: 95vw; + gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); + + /* Dynamic sizing based on number of stimuli and screen space */ + --base-size: min( + calc(85vw / var(--stimuli-count, 5)), + calc(70vh / 1), + 20vmin + ); + --min-size: 4vmin; + --max-size: 25vmin; + --svg-size: max(var(--min-size), min(var(--max-size), var(--base-size))); + + /* Special handling for many stimuli (> 7) to ensure readability */ + --many-stimuli-adjustment: calc(max(0, var(--stimuli-count, 5) - 7) * 0.5vmin); + --adjusted-svg-size: max(calc(var(--svg-size) + var(--many-stimuli-adjustment)), var(--min-size)); +} + +.flanker-stim span { + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + vertical-align: middle; + line-height: 0; +} + +.flanker-stim svg { + width: var(--adjusted-svg-size, var(--svg-size)); + height: var(--adjusted-svg-size, var(--svg-size)); + display: block; +} + +/* Practice highlighting */ +.flanker-stim.practice .center.highlighted { + background-color: #fff3cd; + border: 3px solid #ffc107; + border-radius: 50%; + padding: 1.5vmin; + animation: pulse 1.5s infinite; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); + } + 70% { + box-shadow: 0 0 0 2vmin rgba(255, 193, 7, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); + } +} + +/* Fixation cross */ +.fixation { + display: flex; + align-items: center; + justify-content: center; + min-height: 20vh; + font-size: 6vmin; + font-weight: bold; + color: #2c3e50; +} + +/* Feedback */ +.feedback { + display: flex; + align-items: center; + justify-content: center; + min-height: 20vh; + font-size: 6vmin; + font-weight: bold; + text-align: center; + padding: 3vmin; +} + +.feedback.correct { + color: #27ae60; +} + +.feedback.incorrect { + color: #e74c3c; +} + +/* ============================================================================ */ +/* JSPSYCH BUTTON OVERRIDES FOR MOBILE */ +/* ============================================================================ */ + +/* Button group layout */ +.jspsych-btn-group, +.jspsych-content .jspsych-btn-group, +#jspsych-html-button-response-btngroup { + display: flex !important; + flex-wrap: wrap !important; + gap: 3vmin !important; + max-width: 95vw !important; + margin: 4vmin auto !important; + justify-content: center !important; + align-items: center !important; +} + +/* All jsPsych buttons - mobile-friendly */ +.jspsych-btn { + border: 3px solid #3498db !important; + border-radius: 3vmin !important; + background-color: white !important; + color: #2c3e50 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + cursor: pointer !important; + transition: all 0.3s ease !important; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15) !important; + padding: 3vmin 6vmin !important; + margin: 1vmin !important; + font-family: inherit !important; + font-weight: bold !important; + font-size: 4vmin !important; + + /* Minimum touch target size */ + min-width: 15vmin !important; + min-height: 12vmin !important; +} + +/* Arrow response buttons - bigger for main task */ +.jspsych-btn:only-child { + /* Single buttons like Continue */ + min-width: 25vmin !important; + border-color: #27ae60 !important; + background-color: #2ecc71 !important; + color: white !important; +} + +/* For Left/Right arrow buttons (when there are exactly 2 buttons) */ +.jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + min-width: 20vmin !important; + min-height: 20vmin !important; + font-size: 8vmin !important; + flex: 1 1 auto !important; + max-width: 35vmin !important; +} + +/* Button hover states */ +.jspsych-btn:hover { + border-color: #2980b9 !important; + transform: translateY(-1vmin) !important; + box-shadow: 0 3vmin 4vmin rgba(0, 0, 0, 0.2) !important; + background-color: #ecf0f1 !important; +} + +.jspsych-btn:only-child:hover { + background-color: #27ae60 !important; + border-color: #229954 !important; + color: white !important; +} + +.jspsych-btn:active { + transform: translateY(0) !important; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.15) !important; +} + +/* ============================================================================ */ +/* RESPONSIVE DESIGN */ +/* ============================================================================ */ + +/* Small screens */ +@media (max-width: 480px) { + .flanker-stim { + gap: max(0.3vmin, calc(85vw / var(--stimuli-count, 5) * 0.08)); + padding: 2vmin; + + /* Tighter sizing for small screens */ + --base-size: min( + calc(80vw / var(--stimuli-count, 5)), + calc(50vh / 1), + 15vmin + ); + --min-size: 3vmin; + --max-size: 12vmin; + } + + .trial-prompt { + font-size: 4vmin; + } + + .fixation { + font-size: 10vmin; + min-height: 25vh; + } + + .jspsych-btn { + font-size: 3.5vmin !important; + } + + .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + font-size: 7vmin !important; + min-width: 18vmin !important; + min-height: 18vmin !important; + } +} + +/* Medium screens */ +@media (min-width: 481px) and (max-width: 768px) { + .flanker-stim { + gap: max(0.4vmin, calc(88vw / var(--stimuli-count, 5) * 0.09)); + + /* Medium screen sizing */ + --base-size: min( + calc(82vw / var(--stimuli-count, 5)), + calc(60vh / 1), + 18vmin + ); + --min-size: 3.5vmin; + --max-size: 18vmin; + } + + .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + font-size: 8vmin !important; + min-width: 22vmin !important; + min-height: 22vmin !important; + } +} + +/* Large screens */ +@media (min-width: 769px) { + .flanker-stim { + gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); + + /* Large screen sizing - more generous */ + --base-size: min( + calc(85vw / var(--stimuli-count, 5)), + calc(70vh / 1), + 22vmin + ); + --min-size: 4vmin; + --max-size: 30vmin; + } + + .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { + font-size: 6vmin !important; + min-width: 15vmin !important; + min-height: 15vmin !important; + max-width: 20vmin !important; + } +} + +/* ============================================================================ */ +/* ACCESSIBILITY */ +/* ============================================================================ */ + +/* High contrast */ +@media (prefers-contrast: high) { + .flanker-stim svg { + filter: contrast(2); + } + + .performance-note { + background-color: #ffffff; + border-color: #000000; + color: #000000; + } + + .jspsych-btn { + border-color: #000000 !important; + background-color: #ffffff !important; + color: #000000 !important; + } +} + +/* Reduced motion */ +@media (prefers-reduced-motion: reduce) { + .flanker-stim.practice .center.highlighted { + animation: none; + } + + .jspsych-btn { + transition: none !important; + } +} + +/* Touch devices */ +@media (hover: none) and (pointer: coarse) { + .jspsych-btn:hover { + transform: none !important; + } + + .jspsych-btn:active { + background-color: #3498db !important; + color: white !important; + } + + .jspsych-btn:only-child:active { + background-color: #27ae60 !important; + color: white !important; + } +} \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts new file mode 100644 index 00000000..4e998cc1 --- /dev/null +++ b/packages/flanker-inhibitory/src/text.ts @@ -0,0 +1,152 @@ +/* This file contains the configurable text used in the flanker inhibitory timeline. + * Researchers can modify these texts to change the language or instructions. + */ + +export const trial_text = { + // Button texts + continue_button: "Continue", + start_button: "Start", + ready_button: "I'm Ready", + end_button: "End", + practice_button: "Practice Again", + left_button: "←", + right_button: "→", + + // Task completion messages + task_complete_header: "Task Complete!", + task_complete_message: "Thank you for participating in the flanker inhibitory control task.", + + // Practice phase text + practice_header: "Practice Round", + practice_intro_message: "Let's practice! Look at the middle fish and click the button that shows which way it's swimming.", + practice_complete_header: "Practice Complete!", + practice_complete_message: "Great job! Now you're ready for the real task.", + practice_failed_message: "Let's try the practice again. Remember to focus on the middle fish only.", + + // Main task instructions and headers + main_task_header: "Main Task", + main_task_intro: "Now we'll start the real task. Remember to focus on the middle fish!", + main_task_prompt: "Look at the middle fish. Click the button for the direction it's swimming.", + + // Performance summary labels + performance_title: "Your Performance:", + accuracy_label: "Accuracy:", + response_time_label: "Average Response Time:", + + // Fixation and inter-trial + fixation_cross: "+", + + // Feedback messages + correct_feedback: "Correct!", + incorrect_feedback: "Incorrect", + too_slow_message: "Please respond faster", + + // Progress messages + block_progress: "Block {current} of {total}", + trial_progress: "Trial {current} of {total}", +}; + +// TTS (Text-to-Speech) configuration +export const tts_config = { + // Voice settings + voice_name: '', // Empty string uses default voice, or specify like 'Google US English' + rate: 1.0, // Speech rate (0.1 to 10) + pitch: 1.0, // Speech pitch (0 to 2) + volume: 1.0, // Speech volume (0 to 1) + + // Language and locale + lang: 'en-US', // Language code for speech synthesis + + // What text to speak aloud (can be disabled per element) + speak_instructions: true, // Speak instruction page content + speak_prompts: true, // Speak trial prompts + speak_feedback: true, // Speak correct/incorrect feedback + speak_completion: true, // Speak task completion messages + + // Auto-play settings + auto_speak_on_load: true, // Automatically start speaking when page loads + speak_button_text: false, // Whether to also speak button labels + + // Timing + speech_delay: 500, // Delay before starting speech (ms) + allow_skip: true, // Allow users to skip/stop speech +}; + +// Spoken versions of text (can be different from displayed text for clarity) +export const tts_text = { + // Instruction spoken versions + welcome_spoken: "Welcome to the Flanker Inhibitory Control Task. This is an attention and focus test.", + task_explanation_spoken: "You will see a group of fish swimming on the screen. Your job is to focus only on the middle fish and ignore the other fish around it.", + directions_spoken: "Look at which direction the middle fish is swimming and click the corresponding button. Click the left arrow if it's swimming left, or the right arrow if it's swimming right.", + + // Trial spoken versions + trial_prompt_spoken: "Look at the middle fish. Click the direction it's swimming.", + + // Feedback spoken versions + correct_spoken: "Correct", + incorrect_spoken: "Incorrect", + + // Practice spoken versions + practice_start_spoken: "Let's practice. Look at the middle fish and click the button that shows which way it's swimming.", + practice_complete_spoken: "Practice complete. Great job! Now you're ready for the real task.", + + // Main task spoken versions + main_task_start_spoken: "Now we'll start the real task. Remember to focus on the middle fish.", + task_complete_spoken: "Task complete. Thank you for participating.", + + // Controls + skip_speech_text: "Press space to skip speech", + speech_controls_note: "Speech can be skipped by pressing the space bar at any time.", +}; + +/* + * This is an array of page objects that have configurable texts for the instruction pages + * before the actual trials. Researchers can modify these instructions to change the + * task description, add new instruction pages, or translate to different languages. + * + * Each page object can contain: + * - header: Main title (displays in

    ) + * - header2: Subtitle (displays in

    ) + * - description: Main description text (displays in

    ) + * - task_explanation: Detailed task explanation (displays in

    ) + * - performance_note: Performance tips (displays in

    ) + * - strategy_title: Strategy section title (displays in

    ) + * - strategy_intro: Introduction to strategy (displays in

    ) + * - strategy_points: Array of strategy bullet points (displays as

    • ) + * - start_prompt: Final instruction before starting (displays in

      ) + * - buttons: Array of button text options + * - button_html: Custom HTML for buttons (optional) + */ + +export const instruction_pages = [ + { + header: "Flanker Inhibitory Control Task", + header2: "Attention and Focus Test", + description: "In this task, you will see a group of fish swimming on the screen.", + task_explanation: "Your job is to focus only on the middle fish and ignore the other fish around it.", + performance_note: "Look at which direction the middle fish is swimming and click the corresponding button.", + start_prompt: "Click continue to learn more about the task.", + buttons: ["Continue"], + }, + { + strategy_title: "Instructions", + strategy_intro: "For each trial:", + strategy_points: [ + "Look at the middle fish only - ignore the other fish", + "See which direction the middle fish is swimming", + "Click the ← button if it's swimming left", + "Click the → button if it's swimming right", + "Try to respond as quickly and accurately as possible" + ], + start_prompt: "The other fish around the middle fish might be swimming in different directions - don't let them distract you!", + buttons: ["Continue"], + }, + { + header2: "Button Instructions", + description: "Use these buttons to respond:", + task_explanation: "← Left Button = Middle fish swimming left
      → Right Button = Middle fish swimming right", + performance_note: "Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions.", + start_prompt: "Click continue to start the practice round.", + buttons: ["Continue"], + } +]; \ No newline at end of file diff --git a/packages/spatial-nback/tsconfig.json b/packages/flanker-inhibitory/tsconfig.json similarity index 100% rename from packages/spatial-nback/tsconfig.json rename to packages/flanker-inhibitory/tsconfig.json diff --git a/packages/spatial-nback/README.md b/packages/spatial-nback/README.md deleted file mode 100644 index fdba806c..00000000 --- a/packages/spatial-nback/README.md +++ /dev/null @@ -1,36 +0,0 @@ -# @jspsych-timelines/spatial-nback - -## Overview - -Deployable spatial nback timeline - -## Loading - -*Enter instructions for loading the timeline package here.* - -## Compatibility - -`spatial-nback` requires jsPsych v8.0.0 or later. - -## Documentation - -### createTimeline - -#### jsPsychTimelineSpatialNback.createTimeline(jsPsych, { *options* }) ⇒ timeline -Description of the timeline that this plugin generates - -The following parameters can be specified in the **options** parameter. - -| Parameter | Type | Default | Description | -|-----------|------|---------|-------------| -| parameter | type | default | description | - - -### timelineUnits - - -### utils - -## Author / Citation - -Hunter Farhat \ No newline at end of file diff --git a/packages/spatial-nback/docs/spatial-nback.md b/packages/spatial-nback/docs/spatial-nback.md deleted file mode 100644 index 302e081c..00000000 --- a/packages/spatial-nback/docs/spatial-nback.md +++ /dev/null @@ -1,98 +0,0 @@ -# spatial-nback - -A deployable spatial grid n-back timeline for jsPsych. This module provides a flexible implementation of the spatial n-back task, supporting custom grid sizes, n-back levels, feedback, and more. - -## Parameters - -### Initialization Parameters - -Initialization parameters can be set when calling `initJsPsych()`: - -```js -initJsPsych({ - timelines: [ - {type: jsPsychTimelineSpatialNback, params: {...}} - ] -}) -``` - -Parameter | Type | Default Value | Description -----------|------|---------------|------------ -rows | number | 3 | Number of rows in the grid -cols | number | 3 | Number of columns in the grid -n_back | number | 1 | The n-back level (how many trials back to match) -total_trials | number | 20 | Total number of trials in the timeline -target_percentage | number | 25 | Percentage of trials that are targets -stimulus_duration | number | 750 | Duration (ms) the stimulus is shown -isi_duration | number | 250 | Inter-stimulus interval (ms) -feedback_duration | number | 1000 | Duration (ms) of feedback display -show_feedback_text | boolean | false | Whether to show feedback after each trial -show_feedback_border | boolean | false | Whether to highlight the grid border for feedback -show_feedback_no_click | boolean | false | Show feedback if no response is made -feedback_wait_no_click | boolean | true | Wait for feedback even if no response -cell_size | number | 150 | Size (px) of each grid cell -prompt | string | "Click the button when the position matches the one from {n} trial(s) ago" | Instructions template for each trial -button_text | string | "MATCH" | Text for the match button -stimulus_color | string | "#2196F3" | Color of the stimulus square -correct_color | string | "#4CAF50" | Color for correct feedback -incorrect_color | string | "#F44336" | Color for incorrect feedback -include_instructions | boolean | false | Whether to include instructions at the start -randomize_trials | boolean | false | Randomize the order of trials - -### Trial Parameters - -Trial parameters can be set when adding the timeline to a trial object: - -```js -var trial = { - type: jsPsych..., // your jsPsych plugin type - timelines: [ - {type: jsPsychTimelineSpatialNback, params: {...}} - ] -} -``` - -Parameter | Type | Default Value | Description -----------|------|---------------|------------ -See above | | | (All initialization parameters can also be set per trial, look at @jspsych-contrib/plugin-spatial-nback for more info) - -## Data Generated - -Name | Type | Value ------|------|------ -trial_number | number | The trial index (1-based) -n_back | number | The n-back level for the timeline -total_trials | number | Total number of trials in the timeline -task | string | "spatial-nback" -response | string/number | Participant's response (if applicable) -rt | number | Response time in ms - -## Functions - -The following functions/utilities are exported for custom use: - -### createTimeline(params) -Creates a spatial n-back timeline with the specified parameters. - -### createPracticeTimeline(params) -Creates a short practice timeline (6 trials, 33% targets, feedback enabled). - -### createMultiLevelNBackTimeline(params) -Creates a timeline with multiple n-back levels (e.g., 1-back, 2-back, 3-back). - -### presetConfigurations -Predefined configurations: -- `easy()` -- `medium()` -- `hard()` -- `research()` - -### generateNBackSequence(total_trials, n_back, target_percentage, rows, cols) -Generates the sequence of positions and target flags for the task. - -### instrictions_template -Default instructions template object. - ---- - -See [src/index.ts](src/index.ts) for implementation details. diff --git a/packages/spatial-nback/examples/index.html b/packages/spatial-nback/examples/index.html deleted file mode 100644 index e939484d..00000000 --- a/packages/spatial-nback/examples/index.html +++ /dev/null @@ -1,62 +0,0 @@ - - - - - Spatial N-Back Task - - - - - - - - - - \ No newline at end of file diff --git a/packages/spatial-nback/package-lock.json b/packages/spatial-nback/package-lock.json deleted file mode 100644 index 3b215d1b..00000000 --- a/packages/spatial-nback/package-lock.json +++ /dev/null @@ -1,10419 +0,0 @@ -{ - "name": "@jspsych-timelines/spatial-nback", - "version": "0.0.1", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "@jspsych-timelines/spatial-nback", - "version": "0.0.1", - "license": "MIT", - "dependencies": { - "@jspsych/plugin-html-keyboard-response": "^2.1.0", - "jsPsych": "npm:jspsych@^8.2.1" - }, - "devDependencies": { - "@jspsych/config": "^3.2.2", - "jspsych": "^8.2.1", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz", - "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz", - "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.27.3", - "@babel/helpers": "^7.27.4", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.27.4", - "@babel/types": "^7.27.3", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.5.tgz", - "integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.5", - "@babel/types": "^7.27.3", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", - "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", - "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz", - "integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", - "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz", - "integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.4.tgz", - "integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.3" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.27.1.tgz", - "integrity": "sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", - "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz", - "integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-flow": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", - "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-flow": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.27.1.tgz", - "integrity": "sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-transform-flow-strip-types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-syntax-jsx": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.27.1.tgz", - "integrity": "sha512-K13lQpoV54LATKkzBpBAEu1GGSIRzxR9f4IN4V8DCDgiUMo2UDGagEZr3lPeVNJPLkWUi5JE4hCHKneVTwQlYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/register/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.27.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz", - "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.3", - "@babel/parser": "^7.27.4", - "@babel/template": "^7.27.2", - "@babel/types": "^7.27.3", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.27.3", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.3.tgz", - "integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@citation-js/core": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/@citation-js/core/-/core-0.7.18.tgz", - "integrity": "sha512-EjLuZWA5156dIFGdF7OnyPyWFBW43B8Ckje6Sn/W2RFxHDu0oACvW4/6TNgWT80jhEA4bVFm7ahrZe9MJ2B2UQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/date": "^0.5.0", - "@citation-js/name": "^0.4.2", - "fetch-ponyfill": "^7.1.0", - "sync-fetch": "^0.4.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@citation-js/date": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@citation-js/date/-/date-0.5.1.tgz", - "integrity": "sha512-1iDKAZ4ie48PVhovsOXQ+C6o55dWJloXqtznnnKy6CltJBQLIuLLuUqa8zlIvma0ZigjVjgDUhnVaNU1MErtZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@citation-js/name": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@citation-js/name/-/name-0.4.2.tgz", - "integrity": "sha512-brSPsjs2fOVzSnARLKu0qncn6suWjHVQtrqSUrnqyaRH95r/Ad4wPF5EsoWr+Dx8HzkCGb/ogmoAzfCsqlTwTQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/@citation-js/plugin-bibtex": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-bibtex/-/plugin-bibtex-0.7.18.tgz", - "integrity": "sha512-TdsZSMpgpfcx2NMPu0KiulEoecllwT5EtRUzAJl2pDsdPD1tUqqbyj/NBi0l8fwNy1r7WwAqSFGiqGPjQWpFdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/date": "^0.5.0", - "@citation-js/name": "^0.4.2", - "moo": "^0.5.1" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@citation-js/core": "^0.7.0" - } - }, - "node_modules/@citation-js/plugin-cff": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-cff/-/plugin-cff-0.6.1.tgz", - "integrity": "sha512-tLjTgsfzNOdQWGn5mNc2NAaydHnlRucSERoyAXLN7u0BQBfp7j5zwdxCmxcQD/N7hH3fpDKMG+qDzbqpJuKyNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/date": "^0.5.0", - "@citation-js/plugin-yaml": "^0.6.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@citation-js/plugin-csl": { - "version": "0.7.18", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-csl/-/plugin-csl-0.7.18.tgz", - "integrity": "sha512-cJcOdEZurmtIxNj0d4cOERHpVQJB/mN3YPSDNqfI/xTFRN3bWDpFAsaqubPtMO2ZPpoDS+ZGIP1kggbwCfMmlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/date": "^0.5.0", - "citeproc": "^2.4.6" - }, - "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "@citation-js/core": "^0.7.0" - } - }, - "node_modules/@citation-js/plugin-github": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-github/-/plugin-github-0.6.1.tgz", - "integrity": "sha512-1ZeSgQ5AoYsa8n2acVooUeRk76oA8rLszYNBjzj5z6MPa11BZlQJ9O+Gy4tHjlImvsENLbLPx5f8/V1VHXaCfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/date": "^0.5.0", - "@citation-js/name": "^0.4.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@citation-js/plugin-npm": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-npm/-/plugin-npm-0.6.1.tgz", - "integrity": "sha512-rojJA+l/p2KBpDoY+8n0YfNyQO1Aw03fQR5BN+gXD1LNAP1V+8wqvdPsaHnzPsrhrd4ZXDR7ch/Nk0yynPkJ3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/date": "^0.5.0", - "@citation-js/name": "^0.4.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@citation-js/plugin-software-formats": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-software-formats/-/plugin-software-formats-0.6.1.tgz", - "integrity": "sha512-BDF9rqi56K0hoTgYTVANCFVRSbWKC9V06Uap7oa8SjqCTgnHJAy8t/F3NxsyYPPG+zmRsLW9VNbcIsJOl0eu/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/plugin-cff": "^0.6.1", - "@citation-js/plugin-github": "^0.6.1", - "@citation-js/plugin-npm": "^0.6.1", - "@citation-js/plugin-yaml": "^0.6.1", - "@citation-js/plugin-zenodo": "^0.6.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@citation-js/plugin-yaml": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-yaml/-/plugin-yaml-0.6.1.tgz", - "integrity": "sha512-XEVVks1cJTqRbjy+nmthfw/puR6NwRB3fyJWi1tX13UYXlkhP/h45nsv4zjgLLGekdcMHQvhad9MAYunOftGKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-yaml": "^4.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@citation-js/plugin-zenodo": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@citation-js/plugin-zenodo/-/plugin-zenodo-0.6.1.tgz", - "integrity": "sha512-bUybENHoZqJ6gheUqgkumjI+mu+fA2bg6VoniDmZTb7Qng9iEpi+IWEAR26/vBE0gK0EWrJjczyDW3HCwrhvVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/date": "^0.5.0", - "@citation-js/name": "^0.4.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", - "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", - "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", - "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", - "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", - "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", - "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", - "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", - "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", - "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", - "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", - "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", - "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", - "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", - "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", - "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", - "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", - "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", - "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", - "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", - "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", - "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", - "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", - "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", - "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@gulpjs/messages": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@gulpjs/messages/-/messages-1.1.0.tgz", - "integrity": "sha512-Ys9sazDatyTgZVb4xPlDufLweJ/Os2uHWOv+Caxvy2O85JcnT4M3vc73bi8pdLWlv3fdWQz3pdI9tVwo8rQQSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@gulpjs/to-absolute-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@gulpjs/to-absolute-glob/-/to-absolute-glob-4.0.0.tgz", - "integrity": "sha512-kjotm7XJrJ6v+7knhPaRgaT6q8F8K2jiafwYdNHLzmV0uGLuZY43FK6smNSHUPrhq5kX2slCUy+RGG/xGqmIKA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-negated-glob": "^1.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/@inquirer/checkbox": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.5.0.tgz", - "integrity": "sha512-sMgdETOfi2dUHT8r7TT1BTKOwNvdDGFDXYWtQ2J69SvlYNntk9I/gJe7r5yvMwwsuKnYbuRs3pNhx4tgNck5aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/confirm": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.2.0.tgz", - "integrity": "sha512-oOIwPs0Dvq5220Z8lGL/6LHRTEr9TgLHmiI99Rj1PJ1p1czTys+olrgBqZk4E2qC0YTzeHprxSQmoHioVdJ7Lw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", - "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/figures": "^1.0.6", - "@inquirer/type": "^2.0.0", - "@types/mute-stream": "^0.0.4", - "@types/node": "^22.5.5", - "@types/wrap-ansi": "^3.0.0", - "ansi-escapes": "^4.3.2", - "cli-width": "^4.1.0", - "mute-stream": "^1.0.0", - "signal-exit": "^4.1.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/core/node_modules/@inquirer/type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", - "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", - "dev": true, - "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.2.0.tgz", - "integrity": "sha512-9KHOpJ+dIL5SZli8lJ6xdaYLPPzB8xB9GZItg39MBybzhxA16vxmszmQFrRwbOA918WA2rvu8xhDEg/p6LXKbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "external-editor": "^3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/expand": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.3.0.tgz", - "integrity": "sha512-qnJsUcOGCSG1e5DTOErmv2BPQqrtT6uzqn1vI/aYGiPKq+FgslGZmtdnXbhuI7IlT7OByDoEEqdnhUnVR2hhLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/figures": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.12.tgz", - "integrity": "sha512-MJttijd8rMFcKJC8NYmprWr6hD3r9Gd9qUC0XwPNwoEPWSMVJwA2MlXxF+nhZZNMY+HXsWa+o7KY2emWYIn0jQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/input": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.3.0.tgz", - "integrity": "sha512-XfnpCStx2xgh1LIRqPXrTNEEByqQWoxsWYzNRSEUxJ5c6EQlhMogJ3vHKu8aXuTacebtaZzMAHwEL0kAflKOBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/number": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.1.0.tgz", - "integrity": "sha512-ilUnia/GZUtfSZy3YEErXLJ2Sljo/mf9fiKc08n18DdwdmDbOzRcTv65H1jjDvlsAuvdFXf4Sa/aL7iw/NanVA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/password": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.2.0.tgz", - "integrity": "sha512-5otqIpgsPYIshqhgtEwSspBQE40etouR8VIxzpJkv9i0dVHIpyhiivbkH9/dGiMLdyamT54YRdGJLfl8TFnLHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "ansi-escapes": "^4.3.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/prompts": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.5.0.tgz", - "integrity": "sha512-BHDeL0catgHdcHbSFFUddNzvx/imzJMft+tWDPwTm3hfu8/tApk1HrooNngB2Mb4qY+KaRWF+iZqoVUPeslEog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/checkbox": "^2.5.0", - "@inquirer/confirm": "^3.2.0", - "@inquirer/editor": "^2.2.0", - "@inquirer/expand": "^2.3.0", - "@inquirer/input": "^2.3.0", - "@inquirer/number": "^1.1.0", - "@inquirer/password": "^2.2.0", - "@inquirer/rawlist": "^2.3.0", - "@inquirer/search": "^1.1.0", - "@inquirer/select": "^2.5.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/rawlist": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.3.0.tgz", - "integrity": "sha512-zzfNuINhFF7OLAtGHfhwOW2TlYJyli7lOUoJUXw/uyklcwalV6WRXBXtFIicN8rTRK1XTiPWB4UY+YuW8dsnLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/type": "^1.5.3", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/search": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.1.0.tgz", - "integrity": "sha512-h+/5LSj51dx7hp5xOn4QFnUaKeARwUCLs6mIhtkJ0JYPBLmEYjdHSYh7I6GrLg9LwpJ3xeX0FZgAG1q0QdCpVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/select": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.5.0.tgz", - "integrity": "sha512-YmDobTItPP3WcEI86GvPo+T2sRHkxxOq/kXmsBjHS5BVXUgvgZ5AfJjkvQvZr03T81NnI3KrrRuMzeuYUQRFOA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/figures": "^1.0.5", - "@inquirer/type": "^1.5.3", - "ansi-escapes": "^4.3.2", - "yoctocolors-cjs": "^2.1.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@inquirer/type": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.5.tgz", - "integrity": "sha512-MzICLu4yS7V8AA61sANROZ9vT1H3ooca5dSmI1FjZkzq7o/koMsRfQSzRtFo+F3Ao4Sf1C0bpLKejpKB/+j6MA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mute-stream": "^1.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@isaacs/cliui": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", - "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^5.1.2", - "string-width-cjs": "npm:string-width@^4.2.0", - "strip-ansi": "^7.0.1", - "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", - "wrap-ansi": "^8.1.0", - "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/emoji-regex": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", - "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@isaacs/cliui/node_modules/string-width": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", - "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", - "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.1.0", - "string-width": "^5.0.1", - "strip-ansi": "^7.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^6.0.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@jspsych/config": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@jspsych/config/-/config-3.2.2.tgz", - "integrity": "sha512-nbZe71SxYcdxAL3K/R8kfTSOfek7nbO6D1RrHjVWDbXdYwwUacE0uIKmfjM84UITpRG4JgdH5zIiVLEqd/KOeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@citation-js/core": "^0.7.14", - "@citation-js/plugin-bibtex": "^0.7.14", - "@citation-js/plugin-csl": "^0.7.14", - "@citation-js/plugin-software-formats": "^0.6.1", - "@rollup/plugin-commonjs": "26.0.1", - "@rollup/plugin-node-resolve": "15.2.3", - "@rollup/plugin-replace": "^6.0.1", - "@sucrase/jest-plugin": "3.0.0", - "@types/gulp": "4.0.17", - "@types/jest": "29.5.8", - "@types/node": "^22.10.10", - "alias-hq": "6.2.4", - "app-root-path": "^3.1.0", - "esbuild": "0.23.1", - "glob": "7.2.3", - "gulp": "5.0.0", - "gulp-cli": "3.0.0", - "gulp-file": "0.4.0", - "gulp-rename": "2.0.0", - "gulp-replace": "1.1.4", - "gulp-zip": "6.0.0", - "jest": "29.7.0", - "jest-canvas-mock": "2.5.0", - "jest-environment-jsdom": "29.7.0", - "merge-stream": "2.0.0", - "rollup": "4.21.2", - "rollup-plugin-dts": "6.1.1", - "rollup-plugin-esbuild": "6.1.1", - "rollup-plugin-modify": "^3.0.0", - "rollup-plugin-node-externals": "7.1.3", - "sucrase": "3.34.0", - "tslib": "2.6.2", - "typescript": "^5.7.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@jspsych/plugin-html-keyboard-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", - "integrity": "sha512-NmtYU9798mEJ6CLQREp6bNHo1e+Ca5zwAznBlREmpUMdOrqBhTLrF5T/Gg4t2MJHm4lFN/fwHJtt0g5vMAoYFg==", - "license": "MIT", - "peerDependencies": { - "jspsych": ">=7.1.0" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@pkgjs/parseargs": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", - "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", - "dev": true, - "license": "MIT", - "optional": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "26.0.1", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-26.0.1.tgz", - "integrity": "sha512-UnsKoZK6/aGIH6AdkptXhNvhaqftcjq3zZdT+LY5Ftms6JR06nADcDsYp5hTU9E2lbJUEOhdlY5J4DNTneM+jQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "commondir": "^1.0.1", - "estree-walker": "^2.0.2", - "glob": "^10.4.1", - "is-reference": "1.2.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=16.0.0 || 14 >= 14.17" - }, - "peerDependencies": { - "rollup": "^2.68.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "dev": true, - "license": "ISC", - "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" - }, - "bin": { - "glob": "dist/esm/bin.mjs" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "15.2.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz", - "integrity": "sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "@types/resolve": "1.20.2", - "deepmerge": "^4.2.2", - "is-builtin-module": "^3.2.1", - "is-module": "^1.0.0", - "resolve": "^1.22.1" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^2.78.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/plugin-replace": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz", - "integrity": "sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.1", - "magic-string": "^0.30.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/pluginutils": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", - "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0", - "estree-walker": "^2.0.2", - "picomatch": "^4.0.2" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" - }, - "peerDependenciesMeta": { - "rollup": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.21.2.tgz", - "integrity": "sha512-fSuPrt0ZO8uXeS+xP3b+yYTCBUd05MoSp2N/MFOgjhhUhMmchXlpTQrTpI8T+YAwAQuK7MafsCOxW7VrPMrJcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.21.2.tgz", - "integrity": "sha512-xGU5ZQmPlsjQS6tzTTGwMsnKUtu0WVbl0hYpTPauvbRAnmIvpInhJtgjj3mcuJpEiuUw4v1s4BimkdfDWlh7gA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.21.2.tgz", - "integrity": "sha512-99AhQ3/ZMxU7jw34Sq8brzXqWH/bMnf7ZVhvLk9QU2cOepbQSVTns6qoErJmSiAvU3InRqC2RRZ5ovh1KN0d0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.21.2.tgz", - "integrity": "sha512-ZbRaUvw2iN/y37x6dY50D8m2BnDbBjlnMPotDi/qITMJ4sIxNY33HArjikDyakhSv0+ybdUxhWxE6kTI4oX26w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.21.2.tgz", - "integrity": "sha512-ztRJJMiE8nnU1YFcdbd9BcH6bGWG1z+jP+IPW2oDUAPxPjo9dverIOyXz76m6IPA6udEL12reYeLojzW2cYL7w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.21.2.tgz", - "integrity": "sha512-flOcGHDZajGKYpLV0JNc0VFH361M7rnV1ee+NTeC/BQQ1/0pllYcFmxpagltANYt8FYf9+kL6RSk80Ziwyhr7w==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.21.2.tgz", - "integrity": "sha512-69CF19Kp3TdMopyteO/LJbWufOzqqXzkrv4L2sP8kfMaAQ6iwky7NoXTp7bD6/irKgknDKM0P9E/1l5XxVQAhw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.21.2.tgz", - "integrity": "sha512-48pD/fJkTiHAZTnZwR0VzHrao70/4MlzJrq0ZsILjLW/Ab/1XlVUStYyGt7tdyIiVSlGZbnliqmult/QGA2O2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.21.2.tgz", - "integrity": "sha512-cZdyuInj0ofc7mAQpKcPR2a2iu4YM4FQfuUzCVA2u4HI95lCwzjoPtdWjdpDKyHxI0UO82bLDoOaLfpZ/wviyQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.21.2.tgz", - "integrity": "sha512-RL56JMT6NwQ0lXIQmMIWr1SW28z4E4pOhRRNqwWZeXpRlykRIlEpSWdsgNWJbYBEWD84eocjSGDu/XxbYeCmwg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.21.2.tgz", - "integrity": "sha512-PMxkrWS9z38bCr3rWvDFVGD6sFeZJw4iQlhrup7ReGmfn7Oukrr/zweLhYX6v2/8J6Cep9IEA/SmjXjCmSbrMQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.21.2.tgz", - "integrity": "sha512-B90tYAUoLhU22olrafY3JQCFLnT3NglazdwkHyxNDYF/zAxJt5fJUB/yBoWFoIQ7SQj+KLe3iL4BhOMa9fzgpw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.21.2.tgz", - "integrity": "sha512-7twFizNXudESmC9oneLGIUmoHiiLppz/Xs5uJQ4ShvE6234K0VB1/aJYU3f/4g7PhssLGKBVCC37uRkkOi8wjg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.21.2.tgz", - "integrity": "sha512-9rRero0E7qTeYf6+rFh3AErTNU1VCQg2mn7CQcI44vNUWM9Ze7MSRS/9RFuSsox+vstRt97+x3sOhEey024FRQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.21.2.tgz", - "integrity": "sha512-5rA4vjlqgrpbFVVHX3qkrCo/fZTj1q0Xxpg+Z7yIo3J2AilW7t2+n6Q8Jrx+4MrYpAnjttTYF8rr7bP46BPzRw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.21.2.tgz", - "integrity": "sha512-6UUxd0+SKomjdzuAcp+HAmxw1FlGBnl1v2yEPSabtx4lBfdXHDVsW7+lQkgz9cNFJGY3AWR7+V8P5BqkD9L9nA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^3.0.0" - } - }, - "node_modules/@sucrase/jest-plugin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sucrase/jest-plugin/-/jest-plugin-3.0.0.tgz", - "integrity": "sha512-VRY6YKYImVWiRg1H3Yu24hwB1UPJDSDR62R/n+lOHR3+yDrfHEIAoddJivblMYN6U3vD+ndfTSrecZ9Jl+iGNw==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "jest": ">=27", - "sucrase": ">=3.25.0" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.7" - } - }, - "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/expect": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", - "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/glob-stream": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@types/glob-stream/-/glob-stream-8.0.3.tgz", - "integrity": "sha512-vctgrT9AH/GK3TRaIbRUU0TZn12GBU4kzelZdPyJp1Sc8L/6Wrq21UrtN4+x4saqTg6COUIUtFV6JSYcVln/EQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/picomatch": "*", - "@types/streamx": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/gulp": { - "version": "4.0.17", - "resolved": "https://registry.npmjs.org/@types/gulp/-/gulp-4.0.17.tgz", - "integrity": "sha512-+pKQynu2C/HS16kgmDlAicjtFYP8kaa86eE9P0Ae7GB5W29we/E2TIdbOWtEZD5XkpY+jr8fyqfwO6SWZecLpQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/undertaker": ">=1.2.6", - "@types/vinyl-fs": "*", - "chokidar": "^3.3.1" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", - "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", - "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", - "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.8", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.8.tgz", - "integrity": "sha512-fXEFTxMV2Co8ZF5aYFJv+YeA08RTYJfhtN5c9JSv/mFEMe+xxjufCb+PHL+bJcMs/ebPUsBu+UNTEz+ydXrR6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/jsdom": { - "version": "20.0.1", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz", - "integrity": "sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "node_modules/@types/mute-stream": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", - "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/node": { - "version": "22.15.29", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.29.tgz", - "integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/picomatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-4.0.0.tgz", - "integrity": "sha512-J1Bng+wlyEERWSgJQU1Pi0HObCLVcr994xT/M+1wcl/yNRTGBupsCxthgkdYG+GCOMaQH7iSVUY3LJVBBqG7MQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/resolve": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", - "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/streamx": { - "version": "2.9.5", - "resolved": "https://registry.npmjs.org/@types/streamx/-/streamx-2.9.5.tgz", - "integrity": "sha512-IHYsa6jYrck8VEdSwpY141FTTf6D7boPeMq9jy4qazNrFMA4VbRz/sw5LSsfR7jwdDcx0QKWkUexZvsWBC2eIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/tough-cookie": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", - "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/undertaker": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/undertaker/-/undertaker-1.2.11.tgz", - "integrity": "sha512-j1Z0V2ByRHr8ZK7eOeGq0LGkkdthNFW0uAZGY22iRkNQNL9/vAV0yFPr1QN3FM/peY5bxs9P+1f0PYJTQVa5iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/undertaker-registry": "*", - "async-done": "~1.3.2" - } - }, - "node_modules/@types/undertaker-registry": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@types/undertaker-registry/-/undertaker-registry-1.0.4.tgz", - "integrity": "sha512-tW77pHh2TU4uebWXWeEM5laiw8BuJ7pyJYDh6xenOs75nhny2kVgwYbegJ4BoLMYsIrXaBpKYaPdYO3/udG+hg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/vinyl": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.12.tgz", - "integrity": "sha512-Sr2fYMBUVGYq8kj3UthXFAu5UN6ZW+rYr4NACjZQJvHvj+c8lYv0CahmZ2P/r7iUkN44gGUBwqxZkrKXYPb7cw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/expect": "^1.20.4", - "@types/node": "*" - } - }, - "node_modules/@types/vinyl-fs": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@types/vinyl-fs/-/vinyl-fs-3.0.6.tgz", - "integrity": "sha512-e9GHnmABNUnJ4D99OjVO5s87TfYpmEs7/VKbVS/rt0KkZnKA2vIMyEC5K0H7W/XBiRUO4pdaZxvVUmzjRnrydA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/glob-stream": "*", - "@types/node": "*", - "@types/vinyl": "*" - } - }, - "node_modules/@types/wrap-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", - "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", - "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "deprecated": "Use your platform's native atob() and btoa() methods instead", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz", - "integrity": "sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.1.0", - "acorn-walk": "^8.0.2" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/alias-hq": { - "version": "6.2.4", - "resolved": "https://registry.npmjs.org/alias-hq/-/alias-hq-6.2.4.tgz", - "integrity": "sha512-6KGuO4XB3PbvTfP+WJEJR2dGMy6h0UyLa2/kZOeeD/UIrYoaUAQwKdLovYyCpgZErYD1d3zIuZh6GPMDADvF4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "colors": "^1.4.0", - "get-tsconfig": "^4.8.0", - "glob": "^7.2.3", - "inquirer": "^10.1.6", - "jscodeshift": "^0.16.1", - "json5": "^2.2.3", - "module-alias": "^2.2.3", - "node-fetch": "^2.7.0", - "open": "^7.4.2", - "vue-jscodeshift-adapter": "^2.2.1" - }, - "bin": { - "alias-hq": "bin/alias-hq" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", - "dev": true, - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/app-root-path": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-3.1.0.tgz", - "integrity": "sha512-biN3PwB2gUtjaYy/isrU3aNWI5w+fAfvHkSvCKeQGxhmYpwKFUxudR3Yya+KqVRHBmEDYh+/lTozYCFbmzX4nA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ast-types": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.16.1.tgz", - "integrity": "sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-settle": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-2.0.0.tgz", - "integrity": "sha512-Obu/KE8FurfQRN6ODdHN9LuXqwC+JFIM9NRyZqJJ4ZfLJmIYN9Rg0/kb+wF70VV5+fJusTMQlJ1t5rF7J/ETdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^2.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/async-settle/node_modules/async-done": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", - "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.4", - "once": "^1.4.0", - "stream-exhaust": "^1.0.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/auto-bind": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/auto-bind/-/auto-bind-4.0.0.tgz", - "integrity": "sha512-Hdw8qdNiqdJ8LqT0iK0sVzkFbzg6fhnQqqfWhBDxcHZvU75+B+ayzTy8x+k5Ix0Y92XOhOUlx74ps+bA6BeYMQ==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-import-attributes": "^7.24.7", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/bach": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/bach/-/bach-2.0.1.tgz", - "integrity": "sha512-A7bvGMGiTOxGMpNupYl9HQTf0FFDNF4VCmks4PJpFyN1AX2pdKuxuwdvUz2Hu388wcgp+OvGFNsumBfFNkR7eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^2.0.0", - "async-settle": "^2.0.0", - "now-and-later": "^3.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/bach/node_modules/async-done": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", - "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.4", - "once": "^1.4.0", - "stream-exhaust": "^1.0.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/bare-events": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.5.4.tgz", - "integrity": "sha512-+gFfDkR8pj4/TrWCGUGWmJIkBwuxPS5F+a5yWjOHQt2hHvNZd5YLzadjmDUtFmMM4y429bnKLa8bYBMHcYdnQA==", - "dev": true, - "license": "Apache-2.0", - "optional": true - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bundle-require": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-4.2.1.tgz", - "integrity": "sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "load-tsconfig": "^0.2.3" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "peerDependencies": { - "esbuild": ">=0.17" - } - }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001721", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz", - "integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/citeproc": { - "version": "2.4.63", - "resolved": "https://registry.npmjs.org/citeproc/-/citeproc-2.4.63.tgz", - "integrity": "sha512-68F95Bp4UbgZU/DBUGQn0qV3HDZLCdI9+Bb2ByrTaNJDL5VEm9LqaiNaxljsvoaExSLEXe1/r6n2Z06SCzW3/Q==", - "dev": true, - "license": "CPAL-1.0 OR AGPL-1.0" - }, - "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-props": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-4.0.0.tgz", - "integrity": "sha512-bVWtw1wQLzzKiYROtvNlbJgxgBYt2bMJpkCbKmXM3xyijvcjjWXEk5nyrrT3bgJ7ODb19ZohE2T0Y3FgNPyoTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "each-props": "^3.0.0", - "is-plain-object": "^5.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssfontparser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cssfontparser/-/cssfontparser-1.2.1.tgz", - "integrity": "sha512-6tun4LoZnj7VN6YeegOVb67KBX/7JJsqvj+pv3ZA7F878/eN33AbGa5b/S/wXxS/tcp8nc40xRUrsPlxIyNUPg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true, - "license": "MIT" - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true, - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", - "dev": true, - "license": "MIT" - }, - "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "babel-plugin-macros": "^3.1.0" - }, - "peerDependenciesMeta": { - "babel-plugin-macros": { - "optional": true - } - } - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-file": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "deprecated": "Use your platform's native DOMException instead", - "dev": true, - "license": "MIT", - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/each-props": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-3.0.0.tgz", - "integrity": "sha512-IYf1hpuWrdzse/s/YJOrFmU15lyhSzxelNVAHTEG3DtP4QsLTWZUzcUL3HMXmKQxXpa4EIrBPpwRgj0aehdvAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^5.0.0", - "object.defaults": "^1.1.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true, - "license": "MIT" - }, - "node_modules/easy-transform-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/easy-transform-stream/-/easy-transform-stream-1.0.1.tgz", - "integrity": "sha512-ktkaa6XR7COAR3oj02CF3IOgz2m1hCaY3SfzvKT4Svt2MhHw9XCt+ncJNWfe2TGz31iqzNGZ8spdKQflj+Rlog==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.5.165", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.165.tgz", - "integrity": "sha512-naiMx1Z6Nb2TxPU6fiFrUrDTjyPMLdTtaOd2oLmG8zVSg2hCWGkhPyxwk+qRmZ1ytwVqUv0u7ZcDA5+ALhaUtw==", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", - "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true, - "license": "MIT" - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/esbuild": { - "version": "0.23.1", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", - "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.23.1", - "@esbuild/android-arm": "0.23.1", - "@esbuild/android-arm64": "0.23.1", - "@esbuild/android-x64": "0.23.1", - "@esbuild/darwin-arm64": "0.23.1", - "@esbuild/darwin-x64": "0.23.1", - "@esbuild/freebsd-arm64": "0.23.1", - "@esbuild/freebsd-x64": "0.23.1", - "@esbuild/linux-arm": "0.23.1", - "@esbuild/linux-arm64": "0.23.1", - "@esbuild/linux-ia32": "0.23.1", - "@esbuild/linux-loong64": "0.23.1", - "@esbuild/linux-mips64el": "0.23.1", - "@esbuild/linux-ppc64": "0.23.1", - "@esbuild/linux-riscv64": "0.23.1", - "@esbuild/linux-s390x": "0.23.1", - "@esbuild/linux-x64": "0.23.1", - "@esbuild/netbsd-x64": "0.23.1", - "@esbuild/openbsd-arm64": "0.23.1", - "@esbuild/openbsd-x64": "0.23.1", - "@esbuild/sunos-x64": "0.23.1", - "@esbuild/win32-arm64": "0.23.1", - "@esbuild/win32-ia32": "0.23.1", - "@esbuild/win32-x64": "0.23.1" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/execa/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/execa/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true, - "license": "MIT" - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "license": "MIT", - "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fast-fifo": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", - "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-3.0.0.tgz", - "integrity": "sha512-hKKNajm46uNmTlhHSyZkmToAc56uZJwYq7yrciZjqOxnlfQwERDQJmHPUp7m1m9wx8vgOe8IaCKZ5Kv2k1DdCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fastest-levenshtein": "^1.0.7" - } - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", - "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", - "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", - "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/fetch-ponyfill": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/fetch-ponyfill/-/fetch-ponyfill-7.1.0.tgz", - "integrity": "sha512-FhbbL55dj/qdVO3YNK7ZEkshvj3eQ7EuIGV2I6ic/2YiocvyWv+7jg2s4AyS0wdRU75s3tA8ZxI/xPigb0v5Aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "node-fetch": "~2.6.1" - } - }, - "node_modules/fetch-ponyfill/node_modules/node-fetch": { - "version": "2.6.13", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.13.tgz", - "integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/fetch-ponyfill/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fetch-ponyfill/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/fetch-ponyfill/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-cache-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/findup-sync": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-5.0.0.tgz", - "integrity": "sha512-MzwXju70AuyflbgeOhzvQWAvvQdo1XL0A9bVvlXsYcFEBM87WR4OakL4OfZq+QRmr+duJubio+UtNQCPsVESzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-file": "^1.0.0", - "is-glob": "^4.0.3", - "micromatch": "^4.0.4", - "resolve-dir": "^1.0.1" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/fined": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fined/-/fined-2.0.0.tgz", - "integrity": "sha512-OFRzsL6ZMHz5s0JrsEr+TpdGNCtrVtnuG3x1yzGNiQHT0yaDnXAj8V/lWcpJVrnoDpcwXcASxAZYbuXda2Y82A==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^5.0.0", - "object.defaults": "^1.1.0", - "object.pick": "^1.3.0", - "parse-filepath": "^1.0.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/flagged-respawn": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-2.0.0.tgz", - "integrity": "sha512-Gq/a6YCi8zexmGHMuJwahTGzXlAZAOsbCVKduWXC6TlLCjjFRlExMJc4GC2NYPYZ0r/brw9P7CpRgQmlPVeOoA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/flow-parser": { - "version": "0.272.2", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.272.2.tgz", - "integrity": "sha512-AMMHyzXP4T6ran6yIqaPniH8BDSdJf3T8PJVfnTnPAdILA1tt8nCSxiJAWRk2ZKiuos3OsrO2NWe8XNIcPw+Qw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/for-own": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", - "integrity": "sha512-0OABksIGrxKK8K4kynWkQ7y1zounQxP+CWnyclVwj81KW3vlLlGUx57DKGcP/LH216GzqnstnPocF16Nxs0Ycg==", - "dev": true, - "license": "MIT", - "dependencies": { - "for-in": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/foreground-child": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", - "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", - "dev": true, - "license": "ISC", - "dependencies": { - "cross-spawn": "^7.0.6", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fs-mkdirp-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-2.0.1.tgz", - "integrity": "sha512-UTOY+59K6IA94tec8Wjqm0FSh5OVudGNB0NL/P6fB3HiE3bYOY3VYBGijsnOHNkQSwC1FKkU77pmq7xp9CskLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.8", - "streamx": "^2.12.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/get-tsconfig": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", - "integrity": "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-pkg-maps": "^1.0.0" - }, - "funding": { - "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-stream": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-8.0.3.tgz", - "integrity": "sha512-fqZVj22LtFJkHODT+M4N1RJQ3TjnnQhfE9GwZI8qXscYarnhpip70poMldRnP8ipQ/w0B621kOhfc53/J9bd/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@gulpjs/to-absolute-glob": "^4.0.0", - "anymatch": "^3.1.3", - "fastq": "^1.13.0", - "glob-parent": "^6.0.2", - "is-glob": "^4.0.3", - "is-negated-glob": "^1.0.0", - "normalize-path": "^3.0.0", - "streamx": "^2.12.5" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-stream/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-watcher": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-6.0.0.tgz", - "integrity": "sha512-wGM28Ehmcnk2NqRORXFOTOR064L4imSw3EeOqU5bIwUf62eXGwg89WivH6VMahL8zlQHeodzvHpXplrqzrz3Nw==", - "dev": true, - "license": "MIT", - "dependencies": { - "async-done": "^2.0.0", - "chokidar": "^3.5.3" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/glob-watcher/node_modules/async-done": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-2.0.0.tgz", - "integrity": "sha512-j0s3bzYq9yKIVLKGE/tWlCpa3PfFLcrDZLTSVdnnCTGagXuXBJO4SsY9Xdk/fQBirCkH4evW5xOeJXqlAQFdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.4", - "once": "^1.4.0", - "stream-exhaust": "^1.0.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/global-modules": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", - "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", - "integrity": "sha512-5lsx1NUDHtSjfg0eHlmYvZKv8/nVqX4ckFbM+FrGcQ+04KWcWFo9P5MxPZYSzUvyzmdTbI7Eix8Q4IbELDqzKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/global-prefix/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glogg": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-2.2.0.tgz", - "integrity": "sha512-eWv1ds/zAlz+M1ioHsyKJomfY7jbDDPpwSkv14KQj89bycx1nvK5/2Cj/T9g7kzJcX5Bc7Yv22FjfBZS/jl94A==", - "dev": true, - "license": "MIT", - "dependencies": { - "sparkles": "^2.1.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/gulp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-5.0.0.tgz", - "integrity": "sha512-S8Z8066SSileaYw1S2N1I64IUc/myI2bqe2ihOBzO6+nKpvNSg7ZcWJt/AwF8LC/NVN+/QZ560Cb/5OPsyhkhg==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-watcher": "^6.0.0", - "gulp-cli": "^3.0.0", - "undertaker": "^2.0.0", - "vinyl-fs": "^4.0.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/gulp-cli": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-3.0.0.tgz", - "integrity": "sha512-RtMIitkT8DEMZZygHK2vEuLPqLPAFB4sntSxg4NoDta7ciwGZ18l7JuhCTiS5deOJi2IoK0btE+hs6R4sfj7AA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@gulpjs/messages": "^1.1.0", - "chalk": "^4.1.2", - "copy-props": "^4.0.0", - "gulplog": "^2.2.0", - "interpret": "^3.1.1", - "liftoff": "^5.0.0", - "mute-stdout": "^2.0.0", - "replace-homedir": "^2.0.0", - "semver-greatest-satisfied-range": "^2.0.0", - "string-width": "^4.2.3", - "v8flags": "^4.0.0", - "yargs": "^16.2.0" - }, - "bin": { - "gulp": "bin/gulp.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/gulp-file": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/gulp-file/-/gulp-file-0.4.0.tgz", - "integrity": "sha512-3NPCJpAPpbNoV2aml8T96OK3Aof4pm4PMOIa1jSQbMNSNUUXdZ5QjVgLXLStjv0gg9URcETc7kvYnzXdYXUWug==", - "dev": true, - "license": "BSD", - "dependencies": { - "through2": "^0.4.1", - "vinyl": "^2.1.0" - } - }, - "node_modules/gulp-plugin-extras": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/gulp-plugin-extras/-/gulp-plugin-extras-0.3.0.tgz", - "integrity": "sha512-I/kOBSpo61QsGQZcqozZYEnDseKvpudUafVVWDLYgBFAUJ37kW5R8Sjw9cMYzpGyPUfEYOeoY4p+dkfLqgyJUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/vinyl": "^2.0.9", - "chalk": "^5.3.0", - "easy-transform-stream": "^1.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/gulp-plugin-extras/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/gulp-rename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", - "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/gulp-replace": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.1.4.tgz", - "integrity": "sha512-SVSF7ikuWKhpAW4l4wapAqPPSToJoiNKsbDoUnRrSgwZHH7lH8pbPeQj1aOVYQrbZKhfSVBxVW+Py7vtulRktw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/vinyl": "^2.0.4", - "istextorbinary": "^3.0.0", - "replacestream": "^4.0.3", - "yargs-parser": ">=5.0.0-security.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/gulp-zip": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gulp-zip/-/gulp-zip-6.0.0.tgz", - "integrity": "sha512-fPGvNve2dBoZxGKcviTU7mOa77eQibyhwgGLTxnF+ZCKX8RFaTZKkPbdPnmw0r4TNPRjPCkQB/0VuP+MzgkEYg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-stream": "^8.0.1", - "gulp-plugin-extras": "^0.3.0", - "vinyl": "^3.0.0", - "yazl": "^2.5.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - }, - "peerDependencies": { - "gulp": ">=4" - }, - "peerDependenciesMeta": { - "gulp": { - "optional": true - } - } - }, - "node_modules/gulp-zip/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/gulp-zip/node_modules/vinyl": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", - "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/gulplog": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-2.2.0.tgz", - "integrity": "sha512-V2FaKiOhpR3DRXZuYdRLn/qiY0yI5XmqbTKrYbdemJ+xOh2d2MOweI/XFgMzd/9+1twdvMwllnZbWZNJ+BOm4A==", - "dev": true, - "license": "MIT", - "dependencies": { - "glogg": "^2.2.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", - "integrity": "sha512-eSmmWE5bZTK2Nou4g0AI3zZ9rswp7GRKoKXS1BLUkvPviOqs4YTN1djQIqrXy9k5gEtdLPy86JjRwsNM9tnDcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", - "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-local": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", - "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" - }, - "node_modules/inquirer": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-10.2.2.tgz", - "integrity": "sha512-tyao/4Vo36XnUItZ7DnUXX4f1jVao2mSrleV/5IPtW/XAEA26hRVsbc68nuTEKWcr5vMP/1mVoT2O7u8H4v1Vg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@inquirer/core": "^9.1.0", - "@inquirer/prompts": "^5.5.0", - "@inquirer/type": "^1.5.3", - "@types/mute-stream": "^0.0.4", - "ansi-escapes": "^4.3.2", - "mute-stream": "^1.0.0", - "run-async": "^3.0.0", - "rxjs": "^7.8.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/interpret": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", - "integrity": "sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/is-absolute": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-1.0.0.tgz", - "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-builtin-module": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz", - "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==", - "dev": true, - "license": "MIT", - "dependencies": { - "builtin-modules": "^3.3.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-negated-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", - "integrity": "sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-relative": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-1.0.0.tgz", - "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-unc-path": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-unc-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-1.0.0.tgz", - "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "unc-path-regex": "^0.1.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-valid-glob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-1.0.0.tgz", - "integrity": "sha512-AhiROmoEFDSsjx8hW+5sGwgKVIORcXnrlAx/R0ZSeaPw70Vw0CqkGBBhHGL58Uox2eXnU1AnvXJl1XlyedO5bA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", - "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", - "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.23.9", - "@babel/parser": "^7.23.9", - "@istanbuljs/schema": "^0.1.3", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^7.5.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", - "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^4.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istextorbinary": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-3.3.0.tgz", - "integrity": "sha512-Tvq1W6NAcZeJ8op+Hq7tdZ434rqnMx4CCZ7H0ff83uEloDvVbqAwaMTZcafKGJT0VHkYzuXUiCY4hlXQg6WfoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "binaryextensions": "^2.2.0", - "textextensions": "^3.2.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/jackspeak": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", - "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "@isaacs/cliui": "^8.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - }, - "optionalDependencies": { - "@pkgjs/parseargs": "^0.11.0" - } - }, - "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-canvas-mock": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/jest-canvas-mock/-/jest-canvas-mock-2.5.0.tgz", - "integrity": "sha512-s2bmY2f22WPMzhB2YA93kiyf7CAfWAnV/sFfY9s48IVOrGmwui1eSFluDPesq1M+7tSC1hJAit6mzO0ZNXvVBA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cssfontparser": "^1.2.1", - "moo-color": "^1.0.2" - } - }, - "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-cli/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-cli/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/jest-cli/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", - "integrity": "sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0", - "jsdom": "^20.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "jest-util": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", - "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "leven": "^3.1.0", - "pretty-format": "^29.7.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/joycon": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", - "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jscodeshift": { - "version": "0.16.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.16.1.tgz", - "integrity": "sha512-oMQXySazy63awNBzMpXbbVv73u3irdxTeX2L5ueRyFRxi32qb9uzdZdOY5fTBYADBG19l5M/wnGknZSV1dzCdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.7", - "@babel/parser": "^7.24.7", - "@babel/plugin-transform-class-properties": "^7.24.7", - "@babel/plugin-transform-modules-commonjs": "^7.24.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", - "@babel/plugin-transform-optional-chaining": "^7.24.7", - "@babel/plugin-transform-private-methods": "^7.24.7", - "@babel/preset-flow": "^7.24.7", - "@babel/preset-typescript": "^7.24.7", - "@babel/register": "^7.24.6", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.7", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.23.9", - "temp": "^0.9.4", - "write-file-atomic": "^5.0.1" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - }, - "peerDependenciesMeta": { - "@babel/preset-env": { - "optional": true - } - } - }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", - "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^4.0.1" - }, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/jsdom": { - "version": "20.0.3", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", - "integrity": "sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.8.1", - "acorn-globals": "^7.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.4.2", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.2", - "parse5": "^7.1.1", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.1.2", - "w3c-xmlserializer": "^4.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.11.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jspsych": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/jspsych/-/jspsych-8.2.1.tgz", - "integrity": "sha512-VpAxmkzj72/Y2eTKXt5ksZGFL7VqvgUL+66pUqm7KPUSZsIOSM1rKCtMN42u3fT2N8EonR7LGFFcsAvnZAOykQ==", - "license": "MIT", - "dependencies": { - "auto-bind": "^4.0.0", - "random-words": "^1.1.1", - "seedrandom": "^3.0.5", - "type-fest": "^2.9.0" - } - }, - "node_modules/jspsych/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/last-run": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/last-run/-/last-run-2.0.0.tgz", - "integrity": "sha512-j+y6WhTLN4Itnf9j5ZQos1BGPCS8DAwmgMroR3OzfxAsBxam0hMw7J8M3KqZl0pLQJ1jNnwIexg5DYpC/ctwEQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/lead": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lead/-/lead-4.0.0.tgz", - "integrity": "sha512-DpMa59o5uGUWWjruMp71e6knmwKU3jRBBn1kjuLWN9EeIOxNeSAwvHf03WIl8g/ZMR2oSQC9ej3yeLBwdDc/pg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/liftoff": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-5.0.1.tgz", - "integrity": "sha512-wwLXMbuxSF8gMvubFcFRp56lkFV69twvbU5vDPbaw+Q+/rF8j0HKjGbIdlSi+LuJm9jf7k9PB+nTxnsLMPcv2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "extend": "^3.0.2", - "findup-sync": "^5.0.0", - "fined": "^2.0.0", - "flagged-respawn": "^2.0.0", - "is-plain-object": "^5.0.0", - "rechoir": "^0.8.0", - "resolve": "^1.20.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" - }, - "node_modules/load-tsconfig": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/load-tsconfig/-/load-tsconfig-0.2.5.tgz", - "integrity": "sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/magic-string": { - "version": "0.30.17", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", - "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0" - } - }, - "node_modules/make-dir": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", - "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", - "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/module-alias": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", - "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/moo": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", - "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/moo-color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/moo-color/-/moo-color-1.0.3.tgz", - "integrity": "sha512-i/+ZKXMDf6aqYtBhuOcej71YSlbjT3wCO/4H1j8rPvxDJEifdwgg5MaFyu6iYAT8GBZJg2z0dkgK4YMzvURALQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "^1.1.4" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/mute-stdout": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-2.0.0.tgz", - "integrity": "sha512-32GSKM3Wyc8dg/p39lWPKYu8zci9mJFzV1Np9Of0ZEpe6Fhssn/FbI7ywAMd40uX+p3ZKh3T5EeCFv81qS3HmQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/mz": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", - "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0", - "object-assign": "^4.0.1", - "thenify-all": "^1.0.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.10.5" - } - }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/now-and-later": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-3.0.0.tgz", - "integrity": "sha512-pGO4pzSdaxhWTGkfSfHx3hVzJVslFPwBp2Myq9MYN/ChfJZF87ochMAXnvz6/58RJSf5ik2q9tXprBBrk2cpcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", - "dev": true, - "license": "MIT" - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha512-ncrLw+X55z7bkl5PnUvHwFK9FcGuFYo9gtjws2XtSzL+aZ8tm830P60WJ0dSmFVaSalWieW5MD7kEdnXda9yJw==", - "dev": true, - "license": "MIT" - }, - "node_modules/object.defaults": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object.defaults/-/object.defaults-1.1.0.tgz", - "integrity": "sha512-c/K0mw/F11k4dEUBMW8naXUuBuhxRCfG7W+yFy8EcijU/rSmazOUd1XAEEe6bC0OuXY4HUKjTJv7xbxIMqdxrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", - "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0", - "is-wsl": "^2.1.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ospec": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ospec/-/ospec-3.1.0.tgz", - "integrity": "sha512-+nGtjV3vlADp+UGfL51miAh/hB4awPBkQrArhcgG4trAaoA2gKt5bf9w0m9ch9zOr555cHWaCHZEDiBOkNZSxw==", - "dev": true, - "license": "MIT", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "ospec": "bin/ospec" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/package-json-from-dist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", - "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true, - "license": "BlueOak-1.0.0" - }, - "node_modules/parse-filepath": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.2.tgz", - "integrity": "sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/parse5": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", - "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, - "node_modules/path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha512-QLcPegTHF11axjfojBIoDygmS2E3Lf+8+jI6wOVmNVenrKSo3mFdSGiIgdSHenczw3wPtlVMQaFVwGmM7BJdtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-root-regex": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha512-4GlJ6rZDhQZFE0DPVKh0e9jmZ5egZfxTkp7bcRDuPlJXbAwhxcl2dINPUAsjLdejqaLsCeg8axcLjIbvBjN4pQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" - }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/postcss-load-config": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", - "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^1.10.2" - }, - "engines": { - "node": ">= 10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - "peerDependencies": { - "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "postcss": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true, - "license": "MIT" - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/psl": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "funding": { - "url": "https://github.com/sponsors/lupomontero" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/random-words": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/random-words/-/random-words-1.3.0.tgz", - "integrity": "sha512-brwCGe+DN9DqZrAQVNj1Tct1Lody6GrYL/7uei5wfjeQdacFyFd2h/51LNlOoBMzIKMS9xohuL4+wlF/z1g/xg==", - "license": "MIT", - "dependencies": { - "seedrandom": "^3.0.5" - } - }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/recast": { - "version": "0.23.11", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.23.11.tgz", - "integrity": "sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ast-types": "^0.16.1", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tiny-invariant": "^1.3.3", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", - "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true, - "license": "ISC" - }, - "node_modules/replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/replace-homedir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-homedir/-/replace-homedir-2.0.0.tgz", - "integrity": "sha512-bgEuQQ/BHW0XkkJtawzrfzHFSN70f/3cNOiHa2QsYxqrjaC30X1k74FJ6xswVBP0sr0SpGIdVFuPwfrYziVeyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/replacestream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz", - "integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "escape-string-regexp": "^1.0.3", - "object-assign": "^4.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-dir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", - "integrity": "sha512-R7uiTjECzvOsWSfdM0QKFNBVFcK27aHOUwdvK53BcW8zqnGdYp0Fbj82cy54+2A4P2tFM22J5kRfe1R+lM/1yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-options": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-options/-/resolve-options-2.0.0.tgz", - "integrity": "sha512-/FopbmmFOQCfsCx77BRFdKOniglTiHumLgwvd6IDPihy1GKkadZbgQJBcTb2lMzSR1pndzd96b1nZrreZ7+9/A==", - "dev": true, - "license": "MIT", - "dependencies": { - "value-or-function": "^4.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/resolve-pkg-maps": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", - "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/rollup": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", - "integrity": "sha512-e3TapAgYf9xjdLvKQCkQTnbTKd4a6jwlpQSJJFokHGaX2IVjoEqkIIhiQfqsi0cdwlOD+tQGuOd5AJkc5RngBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.5" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.21.2", - "@rollup/rollup-android-arm64": "4.21.2", - "@rollup/rollup-darwin-arm64": "4.21.2", - "@rollup/rollup-darwin-x64": "4.21.2", - "@rollup/rollup-linux-arm-gnueabihf": "4.21.2", - "@rollup/rollup-linux-arm-musleabihf": "4.21.2", - "@rollup/rollup-linux-arm64-gnu": "4.21.2", - "@rollup/rollup-linux-arm64-musl": "4.21.2", - "@rollup/rollup-linux-powerpc64le-gnu": "4.21.2", - "@rollup/rollup-linux-riscv64-gnu": "4.21.2", - "@rollup/rollup-linux-s390x-gnu": "4.21.2", - "@rollup/rollup-linux-x64-gnu": "4.21.2", - "@rollup/rollup-linux-x64-musl": "4.21.2", - "@rollup/rollup-win32-arm64-msvc": "4.21.2", - "@rollup/rollup-win32-ia32-msvc": "4.21.2", - "@rollup/rollup-win32-x64-msvc": "4.21.2", - "fsevents": "~2.3.2" - } - }, - "node_modules/rollup-plugin-dts": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-dts/-/rollup-plugin-dts-6.1.1.tgz", - "integrity": "sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==", - "dev": true, - "license": "LGPL-3.0-only", - "dependencies": { - "magic-string": "^0.30.10" - }, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/Swatinem" - }, - "optionalDependencies": { - "@babel/code-frame": "^7.24.2" - }, - "peerDependencies": { - "rollup": "^3.29.4 || ^4", - "typescript": "^4.5 || ^5.0" - } - }, - "node_modules/rollup-plugin-esbuild": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-esbuild/-/rollup-plugin-esbuild-6.1.1.tgz", - "integrity": "sha512-CehMY9FAqJD5OUaE/Mi1r5z0kNeYxItmRO2zG4Qnv2qWKF09J2lTy5GUzjJR354ZPrLkCj4fiBN41lo8PzBUhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/pluginutils": "^5.0.5", - "debug": "^4.3.4", - "es-module-lexer": "^1.3.1", - "get-tsconfig": "^4.7.2" - }, - "engines": { - "node": ">=14.18.0" - }, - "peerDependencies": { - "esbuild": ">=0.18.0", - "rollup": "^1.20.0 || ^2.0.0 || ^3.0.0 || ^4.0.0" - } - }, - "node_modules/rollup-plugin-modify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-modify/-/rollup-plugin-modify-3.0.0.tgz", - "integrity": "sha512-p/ffs0Y2jz2dEnWjq1oVC7SY37tuS+aP7whoNaQz1EAAOPg+k3vKJo8cMMWx6xpdd0NzhX4y2YF9o/NPu5YR0Q==", - "dev": true, - "license": "WTFPL", - "dependencies": { - "magic-string": "0.25.2", - "ospec": "3.1.0" - } - }, - "node_modules/rollup-plugin-modify/node_modules/magic-string": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", - "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", - "dev": true, - "license": "MIT", - "dependencies": { - "sourcemap-codec": "^1.4.4" - } - }, - "node_modules/rollup-plugin-node-externals": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-externals/-/rollup-plugin-node-externals-7.1.3.tgz", - "integrity": "sha512-RM+7tJAejAoRsCf93TptTSdqUhRA8S78DleihMiu54Kac+uLkd9VIegLPhGnaW3ehZTXh56+R301mFH6j2A7vw==", - "dev": true, - "funding": [ - { - "type": "patreon", - "url": "https://patreon.com/Septh" - }, - { - "type": "paypal", - "url": "https://paypal.me/septh07" - } - ], - "license": "MIT", - "engines": { - "node": ">= 21 || ^20.6.0 || ^18.19.0" - }, - "peerDependencies": { - "rollup": "^3.0.0 || ^4.0.0" - } - }, - "node_modules/rollup/node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "license": "MIT" - }, - "node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/rxjs": { - "version": "7.8.2", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", - "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.1.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true, - "license": "MIT" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/seedrandom": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-3.0.5.tgz", - "integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/semver-greatest-satisfied-range": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-2.0.0.tgz", - "integrity": "sha512-lH3f6kMbwyANB7HuOWRMlLCa2itaCrZJ+SAqqkSZrZKO/cAsk2EOyaKHUtNkVLFyFW9pct22SFesFp3Z7zpA0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "sver": "^1.8.3" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", - "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true, - "license": "MIT" - }, - "node_modules/sparkles": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-2.1.0.tgz", - "integrity": "sha512-r7iW1bDw8R/cFifrD3JnQJX0K1jqT0kprL48BiBpLZLJPmAm34zsVBsK5lc7HirZYZqMW65dOXZgbAGt/I6frg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", - "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/stream-composer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-composer/-/stream-composer-1.0.2.tgz", - "integrity": "sha512-bnBselmwfX5K10AH6L4c8+S5lgZMWI7ZYrz2rvYjCPB2DIMC4Ig8OpxGpNJSxRZ58oti7y1IcNvjBAz9vW5m4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.13.2" - } - }, - "node_modules/stream-exhaust": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz", - "integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==", - "dev": true, - "license": "MIT" - }, - "node_modules/streamx": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", - "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-fifo": "^1.3.2", - "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" - } - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", - "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/string-width-cjs": { - "name": "string-width", - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sucrase": { - "version": "3.34.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", - "integrity": "sha512-70/LQEZ07TEcxiU2dz51FKaE6hCTWC6vr7FOk3Gr0U60C3shtAN+H+BFr9XlYe5xqf3RA8nrc+VIwzCfnxuXJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.2", - "commander": "^4.0.0", - "glob": "7.1.6", - "lines-and-columns": "^1.1.6", - "mz": "^2.7.0", - "pirates": "^4.0.1", - "ts-interface-checker": "^0.1.9" - }, - "bin": { - "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/sucrase/node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sver": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/sver/-/sver-1.8.4.tgz", - "integrity": "sha512-71o1zfzyawLfIWBOmw8brleKyvnbn73oVHNCsu51uPMz/HWiKkkXsI31JjHW5zqXEqnPYkIiHd8ZmL7FCimLEA==", - "dev": true, - "license": "MIT", - "optionalDependencies": { - "semver": "^6.3.0" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/sync-fetch": { - "version": "0.4.5", - "resolved": "https://registry.npmjs.org/sync-fetch/-/sync-fetch-0.4.5.tgz", - "integrity": "sha512-esiWJ7ixSKGpd9DJPBTC4ckChqdOjIwJfYhVHkcQ2Gnm41323p1TRmEI+esTQ9ppD+b5opps2OTEGTCGX5kF+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^5.7.1", - "node-fetch": "^2.6.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/teex": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", - "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - } - }, - "node_modules/temp": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", - "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mkdirp": "^0.5.1", - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-decoder": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", - "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "b4a": "^1.6.4" - } - }, - "node_modules/textextensions": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-3.3.0.tgz", - "integrity": "sha512-mk82dS8eRABNbeVJrEiN5/UMSCliINAuz8mkUwH4SwslkNP//gbEzlWNS5au0z5Dpx40SQxzqZevZkn+WYJ9Dw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/thenify": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", - "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", - "dev": true, - "license": "MIT", - "dependencies": { - "any-promise": "^1.0.0" - } - }, - "node_modules/thenify-all": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", - "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "thenify": ">= 3.1.0 < 4" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/through2": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.4.2.tgz", - "integrity": "sha512-45Llu+EwHKtAZYTPPVn3XZHBgakWMN3rokhEv5hu596XP+cNgplMg+Gj+1nmAvj+L0K7+N49zBKx5rah5u0QIQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "readable-stream": "~1.0.17", - "xtend": "~2.1.1" - } - }, - "node_modules/through2/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tiny-invariant": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", - "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/to-through": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/to-through/-/to-through-3.0.0.tgz", - "integrity": "sha512-y8MN937s/HVhEoBU1SxfHC+wxCHkV1a9gW8eAdTadYh/bGyesZIVcbjI+mSpFbSVwQici/XjBjuUyri1dnXwBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "streamx": "^2.12.5" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/tough-cookie": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz", - "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, - "node_modules/ts-interface-checker": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tsup": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", - "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^4.0.0", - "cac": "^6.7.12", - "chokidar": "^3.5.1", - "debug": "^4.3.1", - "esbuild": "^0.17.6", - "execa": "^5.0.0", - "globby": "^11.0.3", - "joycon": "^3.0.1", - "postcss-load-config": "^3.0.1", - "resolve-from": "^5.0.0", - "rollup": "^3.2.5", - "source-map": "0.8.0-beta.0", - "sucrase": "^3.20.3", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=14.18" - }, - "peerDependencies": { - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/tsup/node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/tsup/node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "node_modules/tsup/node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/tsup/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/tsup/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/tsup/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/tsup/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/undertaker": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-2.0.0.tgz", - "integrity": "sha512-tO/bf30wBbTsJ7go80j0RzA2rcwX6o7XPBpeFcb+jzoeb4pfMM2zUeSDIkY1AWqeZabWxaQZ/h8N9t35QKDLPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bach": "^2.0.1", - "fast-levenshtein": "^3.0.0", - "last-run": "^2.0.0", - "undertaker-registry": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/undertaker-registry": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/undertaker-registry/-/undertaker-registry-2.0.0.tgz", - "integrity": "sha512-+hhVICbnp+rlzZMgxXenpvTxpuvA67Bfgtt+O9WOE5jo7w/dyiF1VmoZVIHvP2EkUjsyKyTwYKlLhA+j47m1Ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", - "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^2.0.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/v8flags": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-4.0.1.tgz", - "integrity": "sha512-fcRLaS4H/hrZk9hYwbdRM35D0U8IYMfEClhXxCivOojl+yTRAZH3Zy2sSy6qVCiGbV9YAtPssP6jaChqC9vPCg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/value-or-function": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/value-or-function/-/value-or-function-4.0.0.tgz", - "integrity": "sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/vinyl-contents": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-contents/-/vinyl-contents-2.0.0.tgz", - "integrity": "sha512-cHq6NnGyi2pZ7xwdHSW1v4Jfnho4TEGtxZHw01cmnc8+i7jgR6bRnED/LbrKan/Q7CvVLbnvA5OepnhbpjBZ5Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^5.0.0", - "vinyl": "^3.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-contents/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/vinyl-contents/node_modules/vinyl": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", - "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-fs": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-4.0.2.tgz", - "integrity": "sha512-XRFwBLLTl8lRAOYiBqxY279wY46tVxLaRhSwo3GzKEuLz1giffsOquWWboD/haGf5lx+JyTigCFfe7DWHoARIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fs-mkdirp-stream": "^2.0.1", - "glob-stream": "^8.0.3", - "graceful-fs": "^4.2.11", - "iconv-lite": "^0.6.3", - "is-valid-glob": "^1.0.0", - "lead": "^4.0.0", - "normalize-path": "3.0.0", - "resolve-options": "^2.0.0", - "stream-composer": "^1.0.2", - "streamx": "^2.14.0", - "to-through": "^3.0.0", - "value-or-function": "^4.0.0", - "vinyl": "^3.0.1", - "vinyl-sourcemap": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-fs/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/vinyl-fs/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/vinyl-fs/node_modules/vinyl": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", - "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-sourcemap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-sourcemap/-/vinyl-sourcemap-2.0.0.tgz", - "integrity": "sha512-BAEvWxbBUXvlNoFQVFVHpybBbjW1r03WhohJzJDSfgrrK5xVYIDTan6xN14DlyImShgDRv2gl9qhM6irVMsV0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "convert-source-map": "^2.0.0", - "graceful-fs": "^4.2.10", - "now-and-later": "^3.0.0", - "streamx": "^2.12.5", - "vinyl": "^3.0.0", - "vinyl-contents": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vinyl-sourcemap/node_modules/replace-ext": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-2.0.0.tgz", - "integrity": "sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/vinyl-sourcemap/node_modules/vinyl": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-3.0.1.tgz", - "integrity": "sha512-0QwqXteBNXgnLCdWdvPQBX6FXRHtIH3VhJPTd5Lwn28tJXc34YqSCWUmkOvtJHBmB3gGoPtrOKk3Ts8/kEZ9aA==", - "dev": true, - "license": "MIT", - "dependencies": { - "clone": "^2.1.2", - "remove-trailing-separator": "^1.1.0", - "replace-ext": "^2.0.0", - "teex": "^1.0.1" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/vue-jscodeshift-adapter": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vue-jscodeshift-adapter/-/vue-jscodeshift-adapter-2.2.1.tgz", - "integrity": "sha512-4aTkHYknYgP9uk/465MDZjvrotF6o2RMWDy0t+9RUULfgbkT+rHLrNw8onxOk4Y8fCpgcS81b09afodRZY/LuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "vue-sfc-descriptor-to-string": "^1.0.0", - "vue-template-compiler": "^2.5.13" - } - }, - "node_modules/vue-sfc-descriptor-to-string": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/vue-sfc-descriptor-to-string/-/vue-sfc-descriptor-to-string-1.0.0.tgz", - "integrity": "sha512-VYNMsrIPZQZau5Gk8IVtgonN1quOznP9/pLIF5m2c4R30KCDDe3NwthrsM7lSUY2K4lezcb8j3Wu8cQhBuZEMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^3.2.0" - } - }, - "node_modules/vue-template-compiler": { - "version": "2.7.16", - "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.16.tgz", - "integrity": "sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "de-indent": "^1.0.2", - "he": "^1.2.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", - "integrity": "sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi-cjs": { - "name": "wrap-ansi", - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/xtend": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-2.1.2.tgz", - "integrity": "sha512-vMNKzr2rHP9Dp/e1NQFnLQlwlhp9L/LfvnsVdHxN1f+uggyVI3i08uD14GPvCToPkdsRfyPqIyYGmIk58V98ZQ==", - "dev": true, - "dependencies": { - "object-keys": "~0.4.0" - }, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yazl": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yoctocolors-cjs": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", - "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - } -} diff --git a/packages/spatial-nback/package.json b/packages/spatial-nback/package.json deleted file mode 100644 index f00323f3..00000000 --- a/packages/spatial-nback/package.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "name": "@jspsych-timelines/spatial-nback", - "version": "0.0.2", - "description": "spatial nback task timeline for jsPsych", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.browser.min.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpatialNback --external @jspsych-contrib/plugin-spatial-nback" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/jspsych/jspsych-timelines.git", - "directory": "" - }, - "keywords": [ - "jsPsych" - ], - "author": { - "name": "A. Hunter Farhat", - "url": "'use strict';https://github.com/farhat60" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/jspsych/jspsych-timelines/packages/spatial-nback#readme" - }, - "homepage": "/spatial-nback/README.md", - "devDependencies": { - "@jspsych/config": "^3.2.2", - "jspsych": "^8.2.1", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - }, - "dependencies": { - "@jspsych-contrib/plugin-spatial-nback": "github:farhat60/jspsych-contrib", - "@jspsych/plugin-instructions": "^2.1.0", - "jsPsych": "npm:jspsych@^8.2.1" - } -} diff --git a/packages/spatial-nback/src/index.spec.ts b/packages/spatial-nback/src/index.spec.ts deleted file mode 100644 index 5e1e02b2..00000000 --- a/packages/spatial-nback/src/index.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -import { createTimeline, createPracticeTimeline, createMultiLevelNBackTimeline, presetConfigurations } from "./index"; - -/* Test suite for Spatial N-Back Timeline - In-depth HTML testing is in the Spatial N-Back plugin. */ -describe('createTimeline', () => { - it('should create timeline with default parameters', () => { - const timeline = createTimeline(); - expect(timeline.timeline).toHaveLength(20); - expect(timeline.randomize_order).toBe(false); - expect(timeline.timeline[0].rows).toBe(3); - expect(timeline.timeline[0].cols).toBe(3); - expect(timeline.timeline[0].data.n_back).toBe(1); - }); - - it('should create timeline with custom total_trials', () => { - const timeline = createTimeline({ total_trials: 25 }); - expect(timeline.timeline).toHaveLength(25); - }); - - it('should create timeline with custom grid dimensions', () => { - const timeline = createTimeline({ rows: 4, cols: 5 }); - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].cols).toBe(5); - }); - - it('should set correct n_back in trial data', () => { - const timeline = createTimeline({ n_back: 3 }); - timeline.timeline.forEach(trial => { - expect(trial.data.n_back).toBe(3); - }); - }); - - it('should include instructions when specified', () => { - const timeline = createTimeline({ include_instructions: true }); - expect(timeline.timeline).toHaveLength(2); - expect(timeline.timeline[0].show_clickable_nav).toBeTruthy(); - }); - - it('should randomize trials when specified', () => { - const timeline = createTimeline({ randomize_trials: true }); - expect(timeline.randomize_order).toBe(true); - }); - - it('should set correct trial properties', () => { - const timeline = createTimeline({ - stimulus_duration: 1000, - button_text: 'CLICK', - cell_size: 200 - }); - - expect(timeline.timeline[0].stimulus_duration).toBe(1000); - expect(timeline.timeline[0].button_text).toBe('CLICK'); - expect(timeline.timeline[0].cell_size).toBe(200); - }); - - it('should generate correct trial numbers in data', () => { - const timeline = createTimeline({ total_trials: 5 }); - timeline.timeline.forEach((trial, index) => { - expect(trial.data.trial_number).toBe(index + 1); - expect(trial.data.task).toBe('spatial-nback'); - }); - }); -}); - -describe('createPracticeTimeline', () => { - it('should create practice timeline with default 6 trials', () => { - const timeline = createPracticeTimeline(); - expect(timeline.timeline).toHaveLength(2); // instructions + task - expect(timeline.timeline[1].timeline).toHaveLength(6); - }); - - it('should enable feedback by default', () => { - const timeline = createPracticeTimeline(); - const taskTimeline = timeline.timeline[1]; - expect(taskTimeline.timeline[0].show_feedback_text).toBe(true); - expect(taskTimeline.timeline[0].show_feedback_border).toBe(true); - }); - - it('should accept custom options', () => { - const timeline = createPracticeTimeline({ n_back: 2 }); - const taskTimeline = timeline.timeline[1]; - expect(taskTimeline.timeline[0].data.n_back).toBe(2); - }); -}); - -describe('createMultiLevelNBackTimeline', () => { - it('should create timeline with multiple levels', () => { - const timeline = createMultiLevelNBackTimeline({ - n_backs: [1, 2, 3], - trials_per_level: 10 - }); - - expect(timeline.timeline).toHaveLength(3); - expect(timeline.timeline[0].timeline).toHaveLength(10); - expect(timeline.timeline[1].timeline).toHaveLength(10); - expect(timeline.timeline[2].timeline).toHaveLength(10); - }); - - it('should set correct n_back for each level', () => { - const timeline = createMultiLevelNBackTimeline({ - n_backs: [1, 3], - trials_per_level: 5 - }); - - expect(timeline.timeline[0].timeline[0].data.n_back).toBe(1); - expect(timeline.timeline[1].timeline[0].data.n_back).toBe(3); - }); - - it('should randomize levels when specified', () => { - const timeline = createMultiLevelNBackTimeline({ - randomize_levels: true - }); - - expect(timeline.randomize_order).toBe(true); - }); -}); - -describe('presetConfigurations', () => { - it('should create easy preset with correct parameters', () => { - const timeline = presetConfigurations.easy(); - expect(timeline.timeline).toHaveLength(20); - expect(timeline.timeline[0].data.n_back).toBe(1); - expect(timeline.timeline[0].show_feedback_text).toBe(true); - }); - - it('should create medium preset with correct parameters', () => { - const timeline = presetConfigurations.medium(); - expect(timeline.timeline).toHaveLength(30); - expect(timeline.timeline[0].data.n_back).toBe(2); - expect(timeline.timeline[0].show_feedback_text).toBe(false); - }); - - it('should create hard preset with correct parameters', () => { - const timeline = presetConfigurations.hard(); - expect(timeline.timeline).toHaveLength(40); - expect(timeline.timeline[0].data.n_back).toBe(3); - expect(timeline.timeline[0].rows).toBe(4); - expect(timeline.timeline[0].cols).toBe(4); - }); - - it('should create research preset with multi-level timeline', () => { - const timeline = presetConfigurations.research(); - expect(timeline.timeline).toHaveLength(3); - expect(timeline.randomize_order).toBe(true); - - // Check each level has 50 trials - timeline.timeline.forEach(levelTimeline => { - expect(levelTimeline.timeline).toHaveLength(50); - }); - }); -}); - -describe('timeline instruction customization', () => { - - it('should replace placeholders in trial prompt', () => { - const timeline = createTimeline({ - prompt: "Level {n_back}, Trial {trial} of {total}", - n_back: 2, - total_trials: 5 - }); - - expect(timeline.timeline[0].instructions).toBe("Level 2, Trial 1 of 5"); - expect(timeline.timeline[4].instructions).toBe("Level 2, Trial 5 of 5"); - }); -}); - -describe('timeline structure validation', () => { - it('should ensure first n trials are never targets', () => { - const timeline = createTimeline({ n_back: 3, total_trials: 10 }); - - expect(timeline.timeline[0].is_target).toBe(false); - expect(timeline.timeline[1].is_target).toBe(false); - expect(timeline.timeline[2].is_target).toBe(false); - }); - - it('should maintain consistent data structure across trials', () => { - const timeline = createTimeline({ total_trials: 5 }); - - timeline.timeline.forEach(trial => { - expect(trial.data).toHaveProperty('trial_number'); - expect(trial.data).toHaveProperty('n_back'); - expect(trial.data).toHaveProperty('total_trials'); - expect(trial.data).toHaveProperty('task'); - }); - }); -}); diff --git a/packages/spatial-nback/src/index.ts b/packages/spatial-nback/src/index.ts deleted file mode 100644 index 80e2445e..00000000 --- a/packages/spatial-nback/src/index.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { initJsPsych, JsPsych } from "jspsych" -import jsPsychPluginSpatialNback from "@jspsych-contrib/plugin-spatial-nback"; -import jsPsychInstructions from "@jspsych/plugin-instructions"; -import { trial_text, instruction_pages } from "./text"; - -function createInstructions(instructions = instruction_pages) { - const pages: string[] = []; - // Iterate through instruction texts and replace placeholders - for (const text of instruction_pages) { - pages.push(` -

      ${text.header || ""}

      -

      ${text.header2 || ""}

      -

      ${text.description || ""}

      -

      ${text.task_explanation || ""}

      -

      ${text.performance_note || ""}

      -

      ${text.strategy_title || ""}

      -

      ${text.strategy_intro || ""}

      -
        - ${text.strategy_points?.map(point => `
      • ${point}
      • `).join('') || ""} -
      -

      ${text.start_prompt || ""}

      - `); - } - - // Add the pages using instruction plugin - return { - type: jsPsychInstructions, - pages: pages, - button_label_next: "Continue", - button_label_previous: "Back", - show_clickable_nav: true - }; - -} - -// Generate stimulus sequence for n-back task -function generateNBackSequence(total_trials: number, n_back: number, target_percentage: number, rows: number, cols: number) { - const positions: Array<{row: number, col: number}> = []; - const is_target: boolean[] = []; - - // Generate first n trials (cannot be targets) - for (let i = 0; i < n_back; i++) { - positions.push({ - row: Math.floor(Math.random() * rows), - col: Math.floor(Math.random() * cols) - }); - is_target.push(false); - } - - // Calculate number of targets to place - const n_targets = Math.round((target_percentage / 100) * (total_trials - n_back)); - let targets_placed = 0; - - // Generate remaining trials with targets - for (let i = n_back; i < total_trials; i++) { - const can_be_target = targets_placed < n_targets; - const should_be_target = can_be_target && Math.random() < 0.5; - - if (should_be_target) { - // Make this a target trial (same position as n trials back) - positions.push({ - row: positions[i - n_back].row, - col: positions[i - n_back].col - }); - is_target.push(true); - targets_placed++; - } else { - // Generate non-target position - let new_position: {row: number, col: number}; - do { - new_position = { - row: Math.floor(Math.random() * rows), - col: Math.floor(Math.random() * cols) - }; - } while ( - new_position.row === positions[i - n_back].row && - new_position.col === positions[i - n_back].col - ); - positions.push(new_position); - is_target.push(false); - } - } - - return { positions, is_target }; -} - -export function createTimeline({ - rows = 3, - cols = 3, - n_back = 1, - total_trials = 20, - target_percentage = 25, - stimulus_duration = 750, - isi_duration = 250, - feedback_duration = 1000, - show_feedback_text = false, - show_feedback_border = false, - show_feedback_no_click = false, - feedback_wait_no_click = true, - cell_size = 150, - prompt = trial_text.prompt, - button_text = trial_text.button, - stimulus_color = "#2196F3", - correct_color = "#4CAF50", - incorrect_color = "#F44336", - include_instructions = false, - randomize_trials = false, - instruction_texts = instruction_pages, -}: { - rows?: number, - cols?: number, - n_back?: number, - total_trials?: number, - target_percentage?: number, - stimulus_duration?: number, - isi_duration?: number, - feedback_duration?: number, - show_feedback_text?: boolean, - show_feedback_border?: boolean, - show_feedback_no_click?: boolean, - feedback_wait_no_click?: boolean, - cell_size?: number, - prompt?: string, - button_text?: string, - stimulus_color?: string, - correct_color?: string, - incorrect_color?: string, - include_instructions?: boolean, - randomize_trials?: boolean, - instruction_texts?: typeof instruction_pages, -} = {}) { - - // Generate the sequence - const sequence = generateNBackSequence(total_trials, n_back, target_percentage, rows, cols); - - // Create individual trial objects - const trials = []; - for (let i = 0; i < total_trials; i++) { - const trial_instructions = prompt - .replace(/{n_back}/g, n_back.toString()) - .replace(/{plural}/g, n_back > 1 ? 's' : '') - .replace(/{trial}/g, (i + 1).toString()) - .replace(/{total}/g, total_trials.toString()); - - trials.push({ - type: jsPsychPluginSpatialNback, - rows: rows, - cols: cols, - stimulus_row: sequence.positions[i].row, - stimulus_col: sequence.positions[i].col, - is_target: sequence.is_target[i], - stimulus_duration: stimulus_duration, - isi_duration: isi_duration, - feedback_duration: feedback_duration, - show_feedback_text: show_feedback_text, - show_feedback_border: show_feedback_border, - show_feedback_no_click: show_feedback_no_click, - feedback_wait_no_click: feedback_wait_no_click, - cell_size: cell_size, - instructions: trial_instructions, - button_text: button_text, - stimulus_color: stimulus_color, - correct_color: correct_color, - incorrect_color: incorrect_color, - data: { - trial_number: i + 1, - n_back: n_back, - total_trials: total_trials, - task: 'spatial-nback' - } - }); - } - - // Create the main task timeline - const task_timeline = { - timeline: trials, - randomize_order: randomize_trials - }; - - // Return complete timeline with or without instructions - if (include_instructions) { - const instructions = createInstructions(instruction_texts); - - const nested_timeline = { - timeline: [instructions, task_timeline] - }; - return nested_timeline; - - } else { - return task_timeline; - } -} - -// Create a practice timeline with fewer trials -export function createPracticeTimeline(options: Parameters[0] = {}) { - return createTimeline({ - ...options, - total_trials: 6, - target_percentage: 50, - show_feedback_text: true, - show_feedback_border: true, - include_instructions: true - }); -} - -// Create multiple n-back levels timeline -export function createMultiLevelNBackTimeline({ - n_backs = [1, 2], - trials_per_level = 20, - randomize_levels = false, - ...sharedOptions -}: { - n_backs?: number[], - trials_per_level?: number, - randomize_levels?: boolean, -} & Parameters[0] = {}) { - - const level_timelines = n_backs.map(level => { - return createTimeline({ - ...sharedOptions, - n_back: level, - total_trials: trials_per_level, - include_instructions: false - }); - }); - - return { - timeline: level_timelines, - randomize_order: randomize_levels - }; -} - -// Utility functions for common configurations -export const presetConfigurations = { - easy: () => createTimeline({ - n_back: 1, - total_trials: 20, - target_percentage: 30, - show_feedback_text: true - }), - - medium: () => createTimeline({ - n_back: 2, - total_trials: 30, - target_percentage: 25, - show_feedback_text: false - }), - - hard: () => createTimeline({ - n_back: 3, - total_trials: 40, - target_percentage: 20, - show_feedback_text: false, - rows: 4, - cols: 4 - }), - - research: () => createMultiLevelNBackTimeline({ - n_backs: [1, 2, 3], - trials_per_level: 50, - target_percentage: 25, - show_feedback_text: false, - randomize_levels: true - }) -}; - -// Export individual components for custom use -export { createInstructions, generateNBackSequence }; - -// Export default timeline creator -export default createTimeline; - -export const timelineUnits = { - createPracticeTimeline, - createTimeline, - createMultiLevelNBackTimeline, -}; - -export const utils = { - presetConfigurations, - generateNBackSequence, - createInstructions -} \ No newline at end of file diff --git a/packages/spatial-nback/src/text.ts b/packages/spatial-nback/src/text.ts deleted file mode 100644 index bdad3096..00000000 --- a/packages/spatial-nback/src/text.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* This file contains the configurable text used in the timeline. -replaceable holders: {n_back}, {plural}, {trial}, {total} */ -export const trial_text = { - button: "MATCH", - prompt: "Click the MATCH button if the current position matches the position from {n_back} trial{plural} ago. (trial {trial} of {total})", - correct: "Correct!", - incorrect: "Incorrect!", -} - -/* -* This is an array of page objects that have configurable texts for the instruction pages before the actual trials -* You can make as many pages as you would like, just add a new array and your text (e.g. ,{header2: "new page here"}) -* "page" objects are separated by commas, just as in any array. -* keys, in the order they show on a page: header, header2, description, task_explanation, performance_note, -strategy_title, strategy_intro, strategy_points (array of strings), start_prompt -* You can leave any keys or text options out if you do not want them in any specific page -*/ - -export const instruction_pages = [{ - header: "Spatial N-Back Task", //

      - header2: "Memory for Spatial Locations", //

      - description: "In this task, you will see a grid with blue squares appearing in different positions.", //

      - task_explanation: "Your job is to click the MATCH button whenever the current position is the same as the position from 1 trial ago.", //

      - performance_note: "Try to respond as quickly and accurately as possible.", //

      - - start_prompt: "Click the continue to move to the next page.", //

      in bold and blue -}, //page two, you can add more pages if needed by creating more objects in the array -{ - strategy_title: "Strategy", - strategy_intro: "Before each trial, remember:", - strategy_points: [ - "Watch the grid carefully", - "Remember positions from ONE trial ago", - "Click MATCH only when positions match", - "Respond quickly but accurately" - ], - start_prompt: "Click continue to start the practice trial.", -}]; \ No newline at end of file diff --git a/packages/speeded-matching/README.md b/packages/speeded-matching/README.md deleted file mode 100644 index e76b15ad..00000000 --- a/packages/speeded-matching/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# speeded-matching - -## Overview - -An assessment of processing speed. Participants are asked to identify which of four pictures matches the target picture at the top of the screen. - -## Loading - -### In browser - -```html - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/speeded-matching/jest.config.cjs b/packages/speeded-matching/jest.config.cjs deleted file mode 100644 index bd1aacef..00000000 --- a/packages/speeded-matching/jest.config.cjs +++ /dev/null @@ -1 +0,0 @@ -module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname); \ No newline at end of file diff --git a/packages/speeded-matching/package.json b/packages/speeded-matching/package.json deleted file mode 100644 index 5570891a..00000000 --- a/packages/speeded-matching/package.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "name": "@jspsych-timelines/speeded-matching", - "version": "0.0.1", - "description": "An assessment of processing speed. Participants are asked to identify which of four pictures matches the target picture at the top of the screen.", - "type": "module", - "main": "dist/index.mjs", - "types": "dist/index.d.ts", - "unpkg": "dist/index.browser.min.js", - "scripts": { - "build": "tsup src/index.ts --format esm,iife --sourcemap --dts --treeshake --clean --global-name jsPsychTimelineSpeededMatching", - "test": "jest" - }, - "repository": { - "type": "git", - "url": "https://github.com/farhat60/jspsych-timelines/", - "directory": "packages" - }, - "keywords": [ - "jsPsych" - ], - "author": { - "name": "Hunter Farhat", - "url": "github.com/farhat60" - }, - "license": "MIT", - "bugs": { - "url": "https://github.com/farhat60/jspsych-timelines//issues" - }, - "homepage": "https://github.com/jspsych/jspsych-timelines/packages/speeded-matching/README.md", - "devDependencies": { - "jspsych": "^8.0.0", - "@jspsych/config": "^3.2.2", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - } - } \ No newline at end of file diff --git a/packages/speeded-matching/src/index copy.ts b/packages/speeded-matching/src/index copy.ts deleted file mode 100644 index c320b69e..00000000 --- a/packages/speeded-matching/src/index copy.ts +++ /dev/null @@ -1,528 +0,0 @@ -import { JsPsych, TrialType } from "jspsych" -import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" -import { test_items } from "./test-items" -import { trial_text, instruction_pages } from "./text" - -interface SpeedMatchingConfig { - /** Array of test items (animal SVGs) to use as stimuli */ - test_items?: string[] - /** Number of trials to generate */ - num_trials?: number - /** Number of choice options per trial (default 4) */ - num_choices?: number - /** Enable text-to-speech for instructions and prompts */ - enable_tts?: boolean - /** Maximum time allowed per trial (in ms) */ - trial_timeout?: number - /** Inter-trial interval (in ms) */ - inter_trial_interval?: number - /** Show instruction pages before the task */ - show_instructions?: boolean - /** Show practice round before main task */ - show_practice?: boolean - /** Custom instruction texts */ - instruction_texts?: typeof instruction_pages -} - -/** - * Function to provide text-to-speech functionality - * Researchers can modify speech settings like rate and volume - */ -function speakText(text: string) { - if ('speechSynthesis' in window) { - // Stop any ongoing speech - if ('speechSynthesis' in window) { - speechSynthesis.cancel(); - } - - // Wait a brief moment for cancel to take effect - setTimeout(() => { - // Create and speak the utterance - const utterance = new SpeechSynthesisUtterance(text); - utterance.rate = 0.8; // Slightly slower for clarity - utterance.volume = 0.8; - utterance.voice = speechSynthesis.getVoices()[0] || null; // Use first available voice - speechSynthesis.speak(utterance); - }, 100); - } -} - -/** - * Function to get a random selection of test items for creating choice sets - * This ensures variety in the stimuli presented to participants - */ -function getRandomTestItems(items: string[], count: number = 4): string[] { - const shuffled = [...items].sort(() => 0.5 - Math.random()); - return shuffled.slice(0, Math.min(count, items.length)); -} - -/** - * Function to create a trial set with one target and distractors - * items: array of item strings (e.g., SVGs) - * target_index: index of the item to use as target - * num_choices: total number of choices to present (default 4) - * Returns an object with target and choices array - */ -function createTrialSet(items: string[], target_index: number = 0, num_choices: number = 4) { - if (target_index >= items.length) { - target_index = 0; // fallback to first item - } - - const target = items[target_index]; - const others = items.filter((_, index) => index !== target_index); - const shuffled_others = others.sort(() => 0.5 - Math.random()); - - // Create choices array with target and distractors - const choices = [target, ...shuffled_others.slice(0, num_choices - 1)]; - const shuffled_choices = choices.sort(() => 0.5 - Math.random()); - - // Find where the target ended up after shuffling - const correct_choice_index = shuffled_choices.findIndex(choice => choice === target); - - return { - target: target, - choices: shuffled_choices, - correct_answer: correct_choice_index, - target_index: target_index - }; -} - -/** - * Creates instruction pages with configurable text and TTS support - * Based on the pattern-comparison-task implementation - */ -function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false) { - const instruction_timeline = []; - - // Create each instruction page as a separate trial - instruction_pages_data.forEach((page_data, page_index) => { - - // Build page text for TTS - combine all text elements - const page_text = [ - page_data.header, - page_data.header2, - page_data.description, - page_data.task_explanation, - page_data.performance_note, - page_data.strategy_title, - page_data.strategy_intro, - ...(page_data.strategy_points || []), - page_data.start_prompt, - ].filter(Boolean).join(' '); // Remove undefined strings by filtering out falsy values - - instruction_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -

      -

      ${page_data.header || ""}

      -

      ${page_data.header2 || ""}

      -

      ${page_data.description || ""}

      -

      ${page_data.task_explanation || ""}

      -

      ${page_data.performance_note || ""}

      - ${page_data.strategy_title ? `

      ${page_data.strategy_title}

      ` : ""} - ${page_data.strategy_intro ? `

      ${page_data.strategy_intro}

      ` : ""} - ${page_data.strategy_points ? ` -
        - ${page_data.strategy_points.map(point => `
      • ${point}
      • `).join('')} -
      - ` : ""} - ${page_data.start_prompt ? `

      ${page_data.start_prompt}

      ` : ""} -
      - `, - choices: page_data.buttons, - margin_horizontal: '15px', - margin_vertical: '10px', - button_html: function(choice, choice_index) { - // Check if custom button HTML is provided for this page - if ('button_html' in page_data && page_data.button_html && page_data.button_html[choice_index]) { - return page_data.button_html[choice_index].replace('{choice}', choice); - } - // Default button styling - return ``; - }, - ...(enable_tts && { - on_start: function() { - // Stop any ongoing speech first - speechSynthesis.cancel(); - // Speak the page text - if (page_text.trim()) { - speakText(page_text); - } - } - }), - on_finish: function(data: any) { - // Stop speech when moving to next page - speechSynthesis.cancel(); - }, - data: { - page_index: page_index, - task: 'instruction-page' - } - }); - }); - - return { - timeline: instruction_timeline - }; -} - -/** - * Creates a practice round with voice instructions and visual demonstrations - * This helps participants understand the task before the actual trials - */ -function createPracticeRound(items: string[], enable_tts: boolean = false) { - const practice_timeline = []; - - // Create a practice trial set using the first test item - const practice_set = createTrialSet(items, 0, 4); - - // Practice instruction screen - practice_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -

      ${trial_text.practice_header}

      -

      We'll now do a practice round to show you how the task works.

      -
      - `, - choices: [trial_text.continue_button], - button_html: function(choice) { - return ``; - }, - data: { - task: 'practice-instruction' - } - }); - - // Practice trial with voice instruction "Look at this picture" - practice_timeline.push({ - type: HtmlButtonResponsePlugin, - - stimulus: ` -
      -
      -

      ${trial_text.practice_look_instruction}

      -
      -
      -
      - ${practice_set.target} -
      -
      -
      - `, - choices: [], - trial_duration: 3000, // Show for 3 seconds - on_start: function() { - if (enable_tts) { - // Wait for voices to load if needed - if (speechSynthesis.getVoices().length === 0) { - speechSynthesis.addEventListener('voiceschanged', () => { - speakText(trial_text.practice_look_instruction); - }, { once: true }); - } else { - speakText(trial_text.practice_look_instruction); - } - } - }, - data: { - task: 'practice-target-demo' - } - }); - - // Practice trial with voice instruction "We are going to tap the picture down here" - practice_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -
      -

      ${trial_text.practice_tap_instruction}

      -
      -
      -
      - ${practice_set.target} -
      -
      -
      - `, - choices: practice_set.choices.map((_, i) => i.toString()), - button_html: function(choice, choice_index) { - return ``; - }, - trial_duration: 4000, // Show for 4 seconds - on_start: function() { - if (enable_tts) { - // Wait for voices to load if needed - if (speechSynthesis.getVoices().length === 0) { - speechSynthesis.addEventListener('voiceschanged', () => { - speakText(trial_text.practice_tap_instruction); - }, { once: true }); - } else { - speakText(trial_text.practice_tap_instruction); - } - } - }, - on_load: function() { - // Set CSS custom property for number of choices for dynamic sizing - const btnGroup = document.querySelector('.jspsych-btn-group, #jspsych-html-button-response-btngroup') as HTMLElement; - if (btnGroup) { - btnGroup.style.setProperty('--num-choices', practice_set.choices.length.toString()); - } - - // Add flashing animation for practice demo (1.5 seconds total) - setTimeout(() => { - const buttons = document.querySelectorAll('.choice-option'); - buttons.forEach(button => { - button.classList.add('flash-choices'); - // Remove flash class after 1.5 seconds - setTimeout(() => { - button.classList.remove('flash-choices'); - }, 1500); - }); - }, 500); - }, - data: { - task: 'practice-choices-demo' - } - }); - - return practice_timeline; -} - -/** - * Creates ready screen asking if user is ready for the actual test - */ -function createReadyScreen() { - return { - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -

      Practice Complete!

      -

      ${trial_text.practice_complete_message}

      -
      - `, - choices: [trial_text.ready_button], - button_html: function(choice) { - return ``; - }, - data: { - task: 'ready-screen' - } - }; -} - -/** - * Generates trials for the main speeded matching task - * Each trial presents a target and multiple choice options - */ -function generateTrials(config: SpeedMatchingConfig) { - const items = config.test_items || test_items; - const num_trials = config.num_trials || 20; - const num_choices = config.num_choices || 4; - const trials = []; - - for (let i = 0; i < num_trials; i++) { - // Randomly select a target item - const target_index = Math.floor(Math.random() * items.length); - const trial_set = createTrialSet(items, target_index, num_choices); - - trials.push({ - target: trial_set.target, - choices: trial_set.choices, - correct_answer: trial_set.correct_answer, - target_index: trial_set.target_index, - trial_number: i + 1 - }); - } - - return trials; -} - -/** - * Main function to create the complete speeded matching timeline - * Includes instructions, practice, and test phases based on configuration - */ -export function createTimeline(jsPsych: JsPsych, config: SpeedMatchingConfig = {}) { - const { - enable_tts = true, - trial_timeout = 10000, - inter_trial_interval = 500, - show_instructions = true, - show_practice = true, - instruction_texts = instruction_pages - } = config; - - const items = config.test_items || test_items; - const trials = generateTrials(config); - const timeline = []; - - // Add instructions if requested - if (show_instructions) { - const instructions = createInstructions(instruction_texts, enable_tts); - timeline.push(instructions); - } - - // Add practice round if requested - if (show_practice) { - const practice_round = createPracticeRound(items, enable_tts); - practice_round.forEach(trial => timeline.push(trial)); - - // Add ready screen after practice - timeline.push(createReadyScreen()); - } - - // Create main task trials - trials.forEach((trial, index) => { - // Main trial - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -
      -

      ${trial_text.main_task_prompt}

      -
      -
      -
      - ${trial.target} -
      -
      -
      - `, - choices: trial.choices.map((_, i) => i.toString()), - button_html: function(choice, choice_index) { - return ``; - }, - trial_duration: trial_timeout, - on_load: function() { - // Set CSS custom property for number of choices for dynamic sizing - const btnGroup = document.querySelector('.jspsych-btn-group, #jspsych-html-button-response-btngroup') as HTMLElement; - if (btnGroup) { - btnGroup.style.setProperty('--num-choices', trial.choices.length.toString()); - } - - // Add click handlers for visual feedback - const choices = document.querySelectorAll('.choice-option'); - choices.forEach((choice, index) => { - choice.addEventListener('click', function() { - choice.classList.add('selected'); - }); - }); - }, - data: { - task: 'speeded-matching-trial', - trial_number: trial.trial_number, - correct_answer: trial.correct_answer, - target_index: trial.target_index, - target: trial.target, - choices: trial.choices - }, - on_finish: function(data: any) { - // Calculate accuracy and response time - data.correct = data.response === data.correct_answer; - data.reaction_time = data.rt; - - // Stop any ongoing speech when trial ends - speechSynthesis.cancel(); - } - }); - - // Inter-trial interval (fixation cross) - if (inter_trial_interval > 0 && index < trials.length - 1) { - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: `
      ${trial_text.fixation_cross}
      `, - choices: [], - trial_duration: inter_trial_interval, - data: { - task: 'inter-trial-interval' - } - }); - } - }); - - // End screen - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -

      ${trial_text.task_complete_header}

      -

      ${trial_text.task_complete_message}

      -
      - `, - choices: [trial_text.end_button], - button_html: function(choice) { - return ``; - }, - data: { - task: 'end-screen' - } - }); - - return { - timeline: timeline - }; -} - -export const timelineUnits = { - instructions: "Instructions for the speeded matching task", - practice: "Practice round with voice instructions and demonstrations", - readyScreen: "Confirmation screen before starting the main task", - trial: "Single speeded matching trial with target and choice options", - interTrialInterval: "Fixation cross between trials", - endScreen: "Task completion screen" -} - -export const utils = { - generateTrials, - createInstructions, - createPracticeRound, - createReadyScreen, - speakText, - createTrialSet, - getRandomTestItems, - /** Calculate accuracy and reaction time statistics */ - calculatePerformance: function(data: any[]) { - const trial_data = data.filter(d => d.task === 'speeded-matching-trial'); - const correct = trial_data.filter(d => d.correct).length; - const total = trial_data.length; - const accuracy = total > 0 ? (correct / total) * 100 : 0; - - const valid_rts = trial_data.filter(d => d.correct && d.rt !== null).map(d => d.rt); - const mean_rt = valid_rts.length > 0 ? valid_rts.reduce((a, b) => a + b, 0) / valid_rts.length : null; - - // Calculate performance by target type - const target_performance = {}; - trial_data.forEach(trial => { - const target_index = trial.target_index; - if (!target_performance[target_index]) { - target_performance[target_index] = { - correct: 0, - total: 0, - reaction_times: [] - }; - } - target_performance[target_index].total++; - if (trial.correct) { - target_performance[target_index].correct++; - if (trial.rt !== null) { - target_performance[target_index].reaction_times.push(trial.rt); - } - } - }); - - return { - overall: { - accuracy, - mean_reaction_time: mean_rt, - total_trials: total, - correct_trials: correct - }, - by_target: target_performance - }; - } -} - -// Export text and test items for external use -export { trial_text, instruction_pages, test_items, createInstructions } - -// Default export for convenience -export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/speeded-matching/src/index.spec.ts b/packages/speeded-matching/src/index.spec.ts deleted file mode 100644 index 5a7f5704..00000000 --- a/packages/speeded-matching/src/index.spec.ts +++ /dev/null @@ -1,683 +0,0 @@ -import { JsPsych } from "jspsych"; -import { createTimeline, utils, timelineUnits, trial_text, instruction_pages } from "./index"; -import { test_items } from "./test-items"; - -// Mock jsPsych instance -const mockJsPsych = {} as JsPsych; - -// Mock Web Speech API -const mockSpeechSynthesis = { - cancel: jest.fn(), - speak: jest.fn(), - getVoices: jest.fn().mockReturnValue([{ name: 'Test Voice' }]), - addEventListener: jest.fn() -}; - -const mockSpeechSynthesisUtterance = jest.fn().mockImplementation((text) => ({ - text, - rate: 0.8, - volume: 0.8, - voice: null -})); - -// Set up global mocks -global.speechSynthesis = mockSpeechSynthesis as any; -global.SpeechSynthesisUtterance = mockSpeechSynthesisUtterance as any; -global.window = { speechSynthesis: mockSpeechSynthesis } as any; - -// Mock Math.random for predictable tests -const mockMath = Object.create(global.Math); -mockMath.random = jest.fn(); -global.Math = mockMath; - -// Mock DOM methods -const mockQuerySelector = jest.fn(); -const mockQuerySelectorAll = jest.fn(); -const mockAddEventListener = jest.fn(); -const mockClassList = { - add: jest.fn(), - remove: jest.fn() -}; - -global.document = { - querySelector: mockQuerySelector, - querySelectorAll: mockQuerySelectorAll -} as any; - -describe("Speeded Matching Task", () => { - beforeEach(() => { - jest.clearAllMocks(); - mockMath.random.mockReturnValue(0.5); - mockQuerySelector.mockReturnValue({ - style: { setProperty: jest.fn() } - }); - mockQuerySelectorAll.mockReturnValue([ - { addEventListener: mockAddEventListener, classList: mockClassList }, - { addEventListener: mockAddEventListener, classList: mockClassList } - ]); - }); - - describe("createTimeline", () => { - it("should create a basic timeline with default configuration", () => { - const timeline = createTimeline(mockJsPsych); - - expect(timeline).toBeDefined(); - expect(timeline.timeline).toBeInstanceOf(Array); - expect(timeline.timeline.length).toBeGreaterThan(0); - }); - - it("should create timeline with custom number of trials", () => { - const customTrials = 5; - const timeline = createTimeline(mockJsPsych, { num_trials: customTrials }); - - // Count actual speeded-matching-trial items - const mainTrials = timeline.timeline.filter( - (item: any) => item.data?.task === 'speeded-matching-trial' - ); - expect(mainTrials).toHaveLength(customTrials); - }); - - it("should include instructions when show_instructions is true", () => { - const timeline = createTimeline(mockJsPsych, { show_instructions: true }); - - // Should contain instruction timeline - const hasInstructions = timeline.timeline.some( - (item: any) => item.timeline && item.timeline.some((subItem: any) => - subItem.data?.task === 'instruction-page' - ) - ); - expect(hasInstructions).toBe(true); - }); - - it("should include practice round when show_practice is true", () => { - const timeline = createTimeline(mockJsPsych, { show_practice: true }); - - // Should contain practice trials - const hasPractice = timeline.timeline.some( - (item: any) => item.data?.task?.includes('practice') - ); - expect(hasPractice).toBe(true); - }); - - it("should use custom number of choices", () => { - const customChoices = 6; - const timeline = createTimeline(mockJsPsych, { - num_choices: customChoices, - num_trials: 1, - show_instructions: false, - show_practice: false - }); - - const mainTrial = timeline.timeline.find( - (item: any) => item.data?.task === 'speeded-matching-trial' - ); - expect(mainTrial.choices).toHaveLength(customChoices); - }); - - it("should set custom trial timeout", () => { - const customTimeout = 5000; - const timeline = createTimeline(mockJsPsych, { - trial_timeout: customTimeout, - num_trials: 1, - show_instructions: false, - show_practice: false - }); - - const mainTrial = timeline.timeline.find( - (item: any) => item.data?.task === 'speeded-matching-trial' - ); - expect(mainTrial.trial_duration).toBe(customTimeout); - }); - - it("should use custom test items", () => { - const customItems = [ - '', - '' - ]; - - const timeline = createTimeline(mockJsPsych, { - test_items: customItems, - num_trials: 1, - show_instructions: false, - show_practice: false - }); - - const mainTrial = timeline.timeline.find( - (item: any) => item.data?.task === 'speeded-matching-trial' - ); - expect(customItems).toContain(mainTrial.data.target); - }); - - it("should set custom inter-trial interval", () => { - const customInterval = 1000; - const timeline = createTimeline(mockJsPsych, { - inter_trial_interval: customInterval, - num_trials: 2, - show_instructions: false, - show_practice: false - }); - - const intervalTrial = timeline.timeline.find( - (item: any) => item.data?.task === 'inter-trial-interval' - ); - expect(intervalTrial.trial_duration).toBe(customInterval); - }); - - it("should skip inter-trial interval when set to 0", () => { - const timeline = createTimeline(mockJsPsych, { - inter_trial_interval: 0, - num_trials: 2, - show_instructions: false, - show_practice: false - }); - - const intervalTrials = timeline.timeline.filter( - (item: any) => item.data?.task === 'inter-trial-interval' - ); - expect(intervalTrials).toHaveLength(0); - }); - - it("should include end screen", () => { - const timeline = createTimeline(mockJsPsych, { - num_trials: 1, - show_instructions: false, - show_practice: false - }); - - const endScreen = timeline.timeline.find( - (item: any) => item.data?.task === 'end-screen' - ); - expect(endScreen).toBeDefined(); - expect(endScreen.stimulus).toContain(trial_text.task_complete_header); - }); - }); - - describe("utils.generateTrials", () => { - it("should generate the correct number of trials", () => { - const trials = utils.generateTrials({ num_trials: 10 }); - expect(trials).toHaveLength(10); - }); - - it("should generate trials with required properties", () => { - const trials = utils.generateTrials({ num_trials: 1 }); - const trial = trials[0]; - - expect(trial).toHaveProperty("target"); - expect(trial).toHaveProperty("choices"); - expect(trial).toHaveProperty("correct_answer"); - expect(trial).toHaveProperty("target_index"); - expect(trial).toHaveProperty("trial_number"); - }); - - it("should use default test items when none provided", () => { - const trials = utils.generateTrials({ num_trials: 1 }); - const trial = trials[0]; - - expect(test_items).toContain(trial.target); - expect(trial.choices.every((choice: string) => test_items.includes(choice))).toBe(true); - }); - - it("should use custom test items", () => { - const customItems = [ - 'item1', - 'item2', - 'item3', - 'item4' - ]; - - const trials = utils.generateTrials({ - test_items: customItems, - num_trials: 1 - }); - - const trial = trials[0]; - expect(customItems).toContain(trial.target); - expect(trial.choices.every((choice: string) => customItems.includes(choice))).toBe(true); - }); - - it("should ensure target is included in choices", () => { - const trials = utils.generateTrials({ num_trials: 5 }); - - trials.forEach(trial => { - expect(trial.choices).toContain(trial.target); - expect(trial.choices[trial.correct_answer]).toBe(trial.target); - }); - }); - - it("should generate trials with variety in targets", () => { - // Mock Math.random to return different values for variety - const mockMath = jest.spyOn(Math, 'random'); - - // Provide plenty of different random values for target selection and shuffling - const randomValues = [ - 0.1, 0.5, 0.9, 0.2, 0.7, // Target indices - 0.3, 0.8, 0.4, 0.6, 0.9, // Shuffling values - 0.2, 0.7, 0.1, 0.8, 0.4, // More shuffling values - 0.6, 0.3, 0.9, 0.1, 0.5 // Additional values - ]; - - randomValues.forEach(value => { - mockMath.mockReturnValueOnce(value); - }); - - try { - const trials = utils.generateTrials({ num_trials: 5 }); - - // Check that we get variety in targets - const uniqueTargets = new Set(trials.map(t => t.target)); - expect(uniqueTargets.size).toBeGreaterThan(1); - } finally { - // Always restore the mock - mockMath.mockRestore(); - } - }); - }); - - describe("utils.createTrialSet", () => { - const testItems = ['item1', 'item2', 'item3', 'item4', 'item5']; - - it("should create trial set with correct structure", () => { - const trialSet = utils.createTrialSet(testItems, 0, 4); - - expect(trialSet).toHaveProperty("target"); - expect(trialSet).toHaveProperty("choices"); - expect(trialSet).toHaveProperty("correct_answer"); - expect(trialSet).toHaveProperty("target_index"); - }); - - it("should use specified target index", () => { - const targetIndex = 2; - const trialSet = utils.createTrialSet(testItems, targetIndex, 4); - - expect(trialSet.target).toBe(testItems[targetIndex]); - expect(trialSet.target_index).toBe(targetIndex); - }); - - it("should create correct number of choices", () => { - const numChoices = 3; - const trialSet = utils.createTrialSet(testItems, 0, numChoices); - - expect(trialSet.choices).toHaveLength(numChoices); - }); - - it("should include target in choices", () => { - const trialSet = utils.createTrialSet(testItems, 1, 4); - - expect(trialSet.choices).toContain(trialSet.target); - expect(trialSet.choices[trialSet.correct_answer]).toBe(trialSet.target); - }); - - it("should handle invalid target index", () => { - const trialSet = utils.createTrialSet(testItems, 999, 4); - - expect(trialSet.target).toBe(testItems[0]); // Should fallback to index 0 - expect(trialSet.target_index).toBe(0); - }); - }); - - describe("utils.getRandomTestItems", () => { - const testItems = ['item1', 'item2', 'item3', 'item4', 'item5']; - - it("should return correct number of items", () => { - const items = utils.getRandomTestItems(testItems, 3); - expect(items).toHaveLength(3); - }); - - it("should return all items when count exceeds array length", () => { - const items = utils.getRandomTestItems(testItems, 10); - expect(items).toHaveLength(testItems.length); - }); - - it("should return items from original array", () => { - const items = utils.getRandomTestItems(testItems, 2); - items.forEach(item => { - expect(testItems).toContain(item); - }); - }); - }); - - describe("utils.calculatePerformance", () => { - const mockTrialData = [ - { task: 'speeded-matching-trial', correct: true, rt: 1000, target_index: 0 }, - { task: 'speeded-matching-trial', correct: false, rt: 1500, target_index: 0 }, - { task: 'speeded-matching-trial', correct: true, rt: 800, target_index: 1 }, - { task: 'speeded-matching-trial', correct: true, rt: null, target_index: 1 }, - { task: 'instruction-page', correct: true, rt: 500, target_index: 0 }, // Should be filtered out - ]; - - it("should calculate overall performance correctly", () => { - const performance = utils.calculatePerformance(mockTrialData); - - expect(performance.overall.total_trials).toBe(4); - expect(performance.overall.correct_trials).toBe(3); - expect(performance.overall.accuracy).toBe(75); - expect(performance.overall.mean_reaction_time).toBe(900); // (1000 + 800) / 2 - }); - - it("should calculate performance by target type", () => { - const performance = utils.calculatePerformance(mockTrialData); - - // Target 0: 1 correct out of 2 trials - expect(performance.by_target[0].correct).toBe(1); - expect(performance.by_target[0].total).toBe(2); - expect(performance.by_target[0].reaction_times).toEqual([1000]); - - // Target 1: 2 correct out of 2 trials (one with null rt) - expect(performance.by_target[1].correct).toBe(2); - expect(performance.by_target[1].total).toBe(2); - expect(performance.by_target[1].reaction_times).toEqual([800]); - }); - - it("should handle empty data", () => { - const performance = utils.calculatePerformance([]); - - expect(performance.overall.accuracy).toBe(0); - expect(performance.overall.mean_reaction_time).toBe(null); - expect(performance.overall.total_trials).toBe(0); - }); - - it("should filter out non-task data", () => { - const mixedData = [ - { task: 'speeded-matching-trial', correct: true, rt: 1000, target_index: 0 }, - { task: 'instruction-page', correct: true, rt: 500, target_index: 0 }, - { task: 'practice-demo', correct: false, rt: 2000, target_index: 0 }, - ]; - - const performance = utils.calculatePerformance(mixedData); - expect(performance.overall.total_trials).toBe(1); - }); - }); - - describe("utils.speakText", () => { - beforeEach(() => { - // Clear all mocks before each test to avoid cross-test contamination - jest.clearAllMocks(); - }); - - it("should cancel existing speech and speak new text", (done) => { - utils.speakText("Hello world"); - - expect(mockSpeechSynthesis.cancel).toHaveBeenCalled(); - - // Wait for setTimeout to execute - setTimeout(() => { - try { - expect(mockSpeechSynthesisUtterance).toHaveBeenCalledWith("Hello world"); - expect(mockSpeechSynthesis.speak).toHaveBeenCalled(); - done(); - } catch (error) { - done(error); - } - }, 150); - }); - - it("should set utterance properties correctly", (done) => { - utils.speakText("Test text"); - - setTimeout(() => { - try { - // Check that SpeechSynthesisUtterance was called with the correct text - expect(mockSpeechSynthesisUtterance).toHaveBeenCalledWith("Test text"); - const utteranceCall = mockSpeechSynthesisUtterance.mock.calls[0]; - expect(utteranceCall[0]).toBe("Test text"); - done(); - } catch (error) { - done(error); - } - }, 150); - }); - - it("should not throw error when speechSynthesis is not available", () => { - const originalWindow = global.window; - global.window = {} as any; - - expect(() => utils.speakText("Test")).not.toThrow(); - - global.window = originalWindow; - }); - }); - - describe("utils.createInstructions", () => { - it("should create instruction timeline with default pages", () => { - const instructions = utils.createInstructions(); - - expect(instructions.timeline).toBeInstanceOf(Array); - expect(instructions.timeline.length).toBe(instruction_pages.length); - }); - - it("should create instruction timeline with custom pages", () => { - const customPages = [ - { - header: "Custom Header", - header2: "Custom Subheader", - description: "Custom description", - task_explanation: "Custom task explanation", - performance_note: "Custom performance note", - start_prompt: "Click to continue", - buttons: ["Next"] - } - ]; - - const instructions = utils.createInstructions(customPages); - - expect(instructions.timeline).toHaveLength(1); - expect(instructions.timeline[0].stimulus).toContain("Custom Header"); - expect(instructions.timeline[0].stimulus).toContain("Custom description"); - expect(instructions.timeline[0].choices).toEqual(["Next"]); - }); - - it("should handle pages with strategy points", () => { - const customPages = [ - { - strategy_title: "Strategy", - strategy_intro: "Follow these steps:", - strategy_points: ["Step 1", "Step 2", "Step 3"], - start_prompt: "Ready to begin", - buttons: ["Got it"] - } - ]; - - const instructions = utils.createInstructions(customPages); - const page = instructions.timeline[0]; - - expect(page.stimulus).toContain("Strategy"); - expect(page.stimulus).toContain("Step 1"); - expect(page.stimulus).toContain("Step 2"); - expect(page.stimulus).toContain("Step 3"); - }); - }); - - describe("utils.createPracticeRound", () => { - it("should create practice timeline with correct structure", () => { - const practice = utils.createPracticeRound(test_items); - - expect(practice).toBeInstanceOf(Array); - expect(practice.length).toBeGreaterThan(0); - }); - - it("should include practice instruction screen", () => { - const practice = utils.createPracticeRound(test_items); - - const instructionScreen = practice.find( - (item: any) => item.data?.task === 'practice-instruction' - ); - expect(instructionScreen).toBeDefined(); - }); - - it("should include practice demonstration trials", () => { - const practice = utils.createPracticeRound(test_items); - - const demoTrials = practice.filter( - (item: any) => item.data?.task?.includes('practice') - ); - expect(demoTrials.length).toBeGreaterThan(1); - }); - - it("should use first test item for practice", () => { - const practice = utils.createPracticeRound(test_items); - - const targetDemo = practice.find( - (item: any) => item.data?.task === 'practice-target-demo' - ); - expect(targetDemo.stimulus).toContain(test_items[0]); - }); - }); - - describe("utils.createReadyScreen", () => { - it("should create ready screen with correct structure", () => { - const readyScreen = utils.createReadyScreen(); - - expect(readyScreen).toBeDefined(); - expect(readyScreen.data.task).toBe('ready-screen'); - expect(readyScreen.stimulus).toContain(trial_text.practice_complete_header); - expect(readyScreen.choices).toEqual([trial_text.ready_button]); - }); - }); - - describe("Exports", () => { - it("should export trial_text configuration", () => { - expect(trial_text).toBeDefined(); - expect(trial_text.continue_button).toBe("Continue"); - expect(trial_text.practice_header).toBe("Practice Round"); - expect(trial_text.task_complete_header).toBe("Task Complete!"); - }); - - it("should export instruction_pages configuration", () => { - expect(instruction_pages).toBeDefined(); - expect(instruction_pages).toBeInstanceOf(Array); - expect(instruction_pages.length).toBeGreaterThan(0); - }); - - it("should export timelineUnits descriptions", () => { - expect(timelineUnits).toBeDefined(); - expect(timelineUnits.instructions).toBeDefined(); - expect(timelineUnits.practice).toBeDefined(); - expect(timelineUnits.trial).toBeDefined(); - expect(timelineUnits.interTrialInterval).toBeDefined(); - expect(timelineUnits.endScreen).toBeDefined(); - }); - - it("should export utils object with required functions", () => { - expect(utils).toBeDefined(); - expect(typeof utils.generateTrials).toBe("function"); - expect(typeof utils.createInstructions).toBe("function"); - expect(typeof utils.createPracticeRound).toBe("function"); - expect(typeof utils.createReadyScreen).toBe("function"); - expect(typeof utils.speakText).toBe("function"); - expect(typeof utils.createTrialSet).toBe("function"); - expect(typeof utils.getRandomTestItems).toBe("function"); - expect(typeof utils.calculatePerformance).toBe("function"); - }); - }); - - describe("Trial Data Structure", () => { - it("should create trials with correct data structure", () => { - const timeline = createTimeline(mockJsPsych, { - num_trials: 1, - show_instructions: false, - show_practice: false - }); - - const trial = timeline.timeline.find( - (item: any) => item.data?.task === 'speeded-matching-trial' - ); - - expect(trial.data).toHaveProperty("task", "speeded-matching-trial"); - expect(trial.data).toHaveProperty("trial_number"); - expect(trial.data).toHaveProperty("correct_answer"); - expect(trial.data).toHaveProperty("target_index"); - expect(trial.data).toHaveProperty("target"); - expect(trial.data).toHaveProperty("choices"); - }); - - it("should set correct trial numbers", () => { - const timeline = createTimeline(mockJsPsych, { - num_trials: 3, - show_instructions: false, - show_practice: false - }); - - const trials = timeline.timeline.filter( - (item: any) => item.data?.task === 'speeded-matching-trial' - ); - - trials.forEach((trial: any, index: number) => { - expect(trial.data.trial_number).toBe(index + 1); - }); - }); - }); - - describe("Integration with test_items", () => { - it("should use default test items when none provided", () => { - const timeline = createTimeline(mockJsPsych, { - num_trials: 1, - show_instructions: false, - show_practice: false - }); - - const trial = timeline.timeline.find( - (item: any) => item.data?.task === 'speeded-matching-trial' - ); - - expect(test_items).toContain(trial.data.target); - expect(trial.data.target_index).toBeGreaterThanOrEqual(0); - expect(trial.data.target_index).toBeLessThan(test_items.length); - }); - - it("should handle custom instruction texts", () => { - const customInstructions = [ - { - header: "Custom Task", - description: "This is a custom task", - buttons: ["Start"] - } - ]; - - const timeline = createTimeline(mockJsPsych, { - instruction_texts: customInstructions, - show_instructions: true, - num_trials: 1, - show_practice: false - }); - - const hasCustomInstructions = timeline.timeline.some((item: any) => - item.timeline && item.timeline.some((subItem: any) => - subItem.stimulus && subItem.stimulus.includes("Custom Task") - ) - ); - expect(hasCustomInstructions).toBe(true); - }); - }); - - describe("TTS Integration", () => { - it("should enable TTS when requested", () => { - const timeline = createTimeline(mockJsPsych, { - enable_tts: true, - show_instructions: true, - num_trials: 1 - }); - - // Check that instruction pages have TTS enabled - const hasInstructionsWithTTS = timeline.timeline.some((item: any) => - item.timeline && item.timeline.some((subItem: any) => - typeof subItem.on_start === 'function' || subItem.on_start !== undefined - ) - ); - expect(hasInstructionsWithTTS).toBe(true); - }); - - it("should disable TTS when requested", () => { - const timeline = createTimeline(mockJsPsych, { - enable_tts: false, - show_instructions: true, - num_trials: 1 - }); - - // Instructions should still be created but without TTS - const hasInstructions = timeline.timeline.some((item: any) => - item.timeline && item.timeline.length > 0 - ); - expect(hasInstructions).toBe(true); - }); - }); -}); \ No newline at end of file diff --git a/packages/speeded-matching/src/index.ts b/packages/speeded-matching/src/index.ts deleted file mode 100644 index c26175c9..00000000 --- a/packages/speeded-matching/src/index.ts +++ /dev/null @@ -1,527 +0,0 @@ -import { JsPsych, TrialType } from "jspsych" -import HtmlButtonResponsePlugin from "@jspsych/plugin-html-button-response" -import { test_items } from "./test-items" -import { trial_text, instruction_pages } from "./text" - -interface SpeedMatchingConfig { - /** Array of test items (animal SVGs) to use as stimuli */ - test_items?: string[] - /** Number of trials to generate */ - num_trials?: number - /** Number of choice options per trial (default 4) */ - num_choices?: number - /** Enable text-to-speech for instructions and prompts */ - enable_tts?: boolean - /** Maximum time allowed per trial (in ms) */ - trial_timeout?: number - /** Inter-trial interval (in ms) */ - inter_trial_interval?: number - /** Show instruction pages before the task */ - show_instructions?: boolean - /** Show practice round before main task */ - show_practice?: boolean - /** Custom instruction texts */ - instruction_texts?: typeof instruction_pages -} - -/** - * Function to provide text-to-speech functionality - * Researchers can modify speech settings like rate and volume - */ -function speakText(text: string) { - if ('speechSynthesis' in window) { - // Stop any ongoing speech - if ('speechSynthesis' in window) { - speechSynthesis.cancel(); - } - - // Wait a brief moment for cancel to take effect - setTimeout(() => { - // Create and speak the utterance - const utterance = new SpeechSynthesisUtterance(text); - utterance.rate = 0.8; // Slightly slower for clarity - utterance.volume = 0.8; - utterance.voice = speechSynthesis.getVoices()[0] || null; // Use first available voice - speechSynthesis.speak(utterance); - }, 100); - } -} - -/** - * Function to get a random selection of test items for creating choice sets - * This ensures variety in the stimuli presented to participants - */ -function getRandomTestItems(items: string[], count: number = 4): string[] { - const shuffled = [...items].sort(() => 0.5 - Math.random()); - return shuffled.slice(0, Math.min(count, items.length)); -} - -/** - * Function to create a trial set with one target and distractors - * items: array of item strings (e.g., SVGs) - * target_index: index of the item to use as target - * num_choices: total number of choices to present (default 4) - * Returns an object with target and choices array - */ -function createTrialSet(items: string[], target_index: number = 0, num_choices: number = 4) { - if (target_index >= items.length) { - target_index = 0; // fallback to first item - } - - const target = items[target_index]; - const others = items.filter((_, index) => index !== target_index); - const shuffled_others = others.sort(() => 0.5 - Math.random()); - - // Create choices array with target and distractors - const choices = [target, ...shuffled_others.slice(0, num_choices - 1)]; - const shuffled_choices = choices.sort(() => 0.5 - Math.random()); - - // Find where the target ended up after shuffling - const correct_choice_index = shuffled_choices.findIndex(choice => choice === target); - - return { - target: target, - choices: shuffled_choices, - correct_answer: correct_choice_index, - target_index: target_index - }; -} - -/** - * Creates instruction pages with configurable text and TTS support - * Based on the pattern-comparison-task implementation - */ -function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false) { - const instruction_timeline = []; - - // Create each instruction page as a separate trial - instruction_pages_data.forEach((page_data, page_index) => { - - // Build page text for TTS - combine all text elements - const page_text = [ - page_data.header, - page_data.header2, - page_data.description, - page_data.task_explanation, - page_data.performance_note, - page_data.strategy_title, - page_data.strategy_intro, - ...(page_data.strategy_points || []), - page_data.start_prompt, - ].filter(Boolean).join(' '); // Remove undefined strings by filtering out falsy values - - instruction_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -

      ${page_data.header || ""}

      -

      ${page_data.header2 || ""}

      -

      ${page_data.description || ""}

      -

      ${page_data.task_explanation || ""}

      -

      ${page_data.performance_note || ""}

      - ${page_data.strategy_title ? `

      ${page_data.strategy_title}

      ` : ""} - ${page_data.strategy_intro ? `

      ${page_data.strategy_intro}

      ` : ""} - ${page_data.strategy_points ? ` -
        - ${page_data.strategy_points.map(point => `
      • ${point}
      • `).join('')} -
      - ` : ""} - ${page_data.start_prompt ? `

      ${page_data.start_prompt}

      ` : ""} -
      - `, - choices: page_data.buttons, - margin_horizontal: '15px', - margin_vertical: '10px', - button_html: function(choice, choice_index) { - // Check if custom button HTML is provided for this page - if ('button_html' in page_data && page_data.button_html && page_data.button_html[choice_index]) { - return page_data.button_html[choice_index].replace('{choice}', choice); - } - // Default button styling - return ``; - }, - ...(enable_tts && { - on_start: function() { - // Stop any ongoing speech first - speechSynthesis.cancel(); - // Speak the page text - if (page_text.trim()) { - speakText(page_text); - } - } - }), - on_finish: function(data: any) { - // Stop speech when moving to next page - speechSynthesis.cancel(); - }, - data: { - page_index: page_index, - task: 'instruction-page' - } - }); - }); - - return { - timeline: instruction_timeline - }; -} - -/** - * Creates a practice round with voice instructions and visual demonstrations - * This helps participants understand the task before the actual trials - */ -function createPracticeRound(items: string[], enable_tts: boolean = false) { - const practice_timeline = []; - - // Create a practice trial set using the first test item - const practice_set = createTrialSet(items, 0, 4); - - // Practice instruction screen - practice_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -

      ${trial_text.practice_header}

      -

      ${trial_text.practice_intro_message}

      -
      - `, - choices: [trial_text.continue_button], - button_html: function(choice) { - return ``; - }, - data: { - task: 'practice-instruction' - } - }); - - // Practice trial with voice instruction "Look at this picture" - practice_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -
      -

      ${trial_text.practice_look_instruction}

      -
      -
      -
      - ${practice_set.target} -
      -
      -
      - `, - choices: [], - trial_duration: 3000, // Show for 3 seconds - on_start: function() { - if (enable_tts) { - // Wait for voices to load if needed - if (speechSynthesis.getVoices().length === 0) { - speechSynthesis.addEventListener('voiceschanged', () => { - speakText(trial_text.practice_look_instruction); - }, { once: true }); - } else { - speakText(trial_text.practice_look_instruction); - } - } - }, - data: { - task: 'practice-target-demo' - } - }); - - // Practice trial with voice instruction "We are going to tap the picture down here" - practice_timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -
      -

      ${trial_text.practice_tap_instruction}

      -
      -
      -
      - ${practice_set.target} -
      -
      -
      - `, - choices: practice_set.choices.map((_, i) => i.toString()), - button_html: function(choice, choice_index) { - return ``; - }, - trial_duration: 4000, // Show for 4 seconds - on_start: function() { - if (enable_tts) { - // Wait for voices to load if needed - if (speechSynthesis.getVoices().length === 0) { - speechSynthesis.addEventListener('voiceschanged', () => { - speakText(trial_text.practice_tap_instruction); - }, { once: true }); - } else { - speakText(trial_text.practice_tap_instruction); - } - } - }, - on_load: function() { - // Set CSS custom property for number of choices for dynamic sizing - const btnGroup = document.querySelector('.jspsych-btn-group, #jspsych-html-button-response-btngroup') as HTMLElement; - if (btnGroup) { - btnGroup.style.setProperty('--num-choices', practice_set.choices.length.toString()); - } - - // Add flashing animation for practice demo (1.5 seconds total) - setTimeout(() => { - const buttons = document.querySelectorAll('.choice-option'); - buttons.forEach(button => { - button.classList.add('flash-choices'); - // Remove flash class after 1.5 seconds - setTimeout(() => { - button.classList.remove('flash-choices'); - }, 1500); - }); - }, 500); - }, - data: { - task: 'practice-choices-demo' - } - }); - - return practice_timeline; -} - -/** - * Creates ready screen asking if user is ready for the actual test - */ -function createReadyScreen() { - return { - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -

      ${trial_text.practice_complete_header}

      -

      ${trial_text.practice_complete_message}

      -
      - `, - choices: [trial_text.ready_button], - button_html: function(choice) { - return ``; - }, - data: { - task: 'ready-screen' - } - }; -} - -/** - * Generates trials for the main speeded matching task - * Each trial presents a target and multiple choice options - */ -function generateTrials(config: SpeedMatchingConfig) { - const items = config.test_items || test_items; - const num_trials = config.num_trials || 20; - const num_choices = config.num_choices || 4; - const trials = []; - - for (let i = 0; i < num_trials; i++) { - // Randomly select a target item - const target_index = Math.floor(Math.random() * items.length); - const trial_set = createTrialSet(items, target_index, num_choices); - - trials.push({ - target: trial_set.target, - choices: trial_set.choices, - correct_answer: trial_set.correct_answer, - target_index: trial_set.target_index, - trial_number: i + 1 - }); - } - - return trials; -} - -/** - * Main function to create the complete speeded matching timeline - * Includes instructions, practice, and test phases based on configuration - */ -export function createTimeline(jsPsych: JsPsych, config: SpeedMatchingConfig = {}) { - const { - enable_tts = true, - trial_timeout = 10000, - inter_trial_interval = 500, - show_instructions = true, - show_practice = true, - instruction_texts = instruction_pages - } = config; - - const items = config.test_items || test_items; - const trials = generateTrials(config); - const timeline = []; - - // Add instructions if requested - if (show_instructions) { - const instructions = createInstructions(instruction_texts, enable_tts); - timeline.push(instructions); - } - - // Add practice round if requested - if (show_practice) { - const practice_round = createPracticeRound(items, enable_tts); - practice_round.forEach(trial => timeline.push(trial)); - - // Add ready screen after practice - timeline.push(createReadyScreen()); - } - - // Create main task trials - trials.forEach((trial, index) => { - // Main trial - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -
      -

      ${trial_text.main_task_prompt}

      -
      -
      -
      - ${trial.target} -
      -
      -
      - `, - choices: trial.choices.map((_, i) => i.toString()), - button_html: function(choice, choice_index) { - return ``; - }, - trial_duration: trial_timeout, - on_load: function() { - // Set CSS custom property for number of choices for dynamic sizing - const btnGroup = document.querySelector('.jspsych-btn-group, #jspsych-html-button-response-btngroup') as HTMLElement; - if (btnGroup) { - btnGroup.style.setProperty('--num-choices', trial.choices.length.toString()); - } - - // Add click handlers for visual feedback - const choices = document.querySelectorAll('.choice-option'); - choices.forEach((choice, index) => { - choice.addEventListener('click', function() { - choice.classList.add('selected'); - }); - }); - }, - data: { - task: 'speeded-matching-trial', - trial_number: trial.trial_number, - correct_answer: trial.correct_answer, - target_index: trial.target_index, - target: trial.target, - choices: trial.choices - }, - on_finish: function(data: any) { - // Calculate accuracy and response time - data.correct = data.response === data.correct_answer; - data.reaction_time = data.rt; - - // Stop any ongoing speech when trial ends - speechSynthesis.cancel(); - } - }); - - // Inter-trial interval (fixation cross) - if (inter_trial_interval > 0 && index < trials.length - 1) { - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: `
      ${trial_text.fixation_cross}
      `, - choices: [], - trial_duration: inter_trial_interval, - data: { - task: 'inter-trial-interval' - } - }); - } - }); - - // End screen - timeline.push({ - type: HtmlButtonResponsePlugin, - stimulus: ` -
      -

      ${trial_text.task_complete_header}

      -

      ${trial_text.task_complete_message}

      -
      - `, - choices: [trial_text.end_button], - button_html: function(choice) { - return ``; - }, - data: { - task: 'end-screen' - } - }); - - return { - timeline: timeline - }; -} - -export const timelineUnits = { - instructions: "Instructions for the speeded matching task", - practice: "Practice round with voice instructions and demonstrations", - readyScreen: "Confirmation screen before starting the main task", - trial: "Single speeded matching trial with target and choice options", - interTrialInterval: "Fixation cross between trials", - endScreen: "Task completion screen" -} - -export const utils = { - generateTrials, - createInstructions, - createPracticeRound, - createReadyScreen, - speakText, - createTrialSet, - getRandomTestItems, - /** Calculate accuracy and reaction time statistics */ - calculatePerformance: function(data: any[]) { - const trial_data = data.filter(d => d.task === 'speeded-matching-trial'); - const correct = trial_data.filter(d => d.correct).length; - const total = trial_data.length; - const accuracy = total > 0 ? (correct / total) * 100 : 0; - - const valid_rts = trial_data.filter(d => d.correct && d.rt !== null).map(d => d.rt); - const mean_rt = valid_rts.length > 0 ? valid_rts.reduce((a, b) => a + b, 0) / valid_rts.length : null; - - // Calculate performance by target type - const target_performance = {}; - trial_data.forEach(trial => { - const target_index = trial.target_index; - if (!target_performance[target_index]) { - target_performance[target_index] = { - correct: 0, - total: 0, - reaction_times: [] - }; - } - target_performance[target_index].total++; - if (trial.correct) { - target_performance[target_index].correct++; - if (trial.rt !== null) { - target_performance[target_index].reaction_times.push(trial.rt); - } - } - }); - - return { - overall: { - accuracy, - mean_reaction_time: mean_rt, - total_trials: total, - correct_trials: correct - }, - by_target: target_performance - }; - } -} - -// Export text and test items for external use -export { trial_text, instruction_pages, test_items, createInstructions } - -// Default export for convenience -export default { createTimeline, timelineUnits, utils } \ No newline at end of file diff --git a/packages/speeded-matching/src/styles.css b/packages/speeded-matching/src/styles.css deleted file mode 100644 index 4488a934..00000000 --- a/packages/speeded-matching/src/styles.css +++ /dev/null @@ -1,266 +0,0 @@ -/* Speeded Matching Task - Minimal Styles */ - -.speeded-matching-container { - display: flex; - flex-direction: column; - align-items: center; - justify-content: flex-start; - font-family: Arial, sans-serif; - padding: 2vmin; -} - -/* Instructions */ -.speeded-matching-instructions-container { - max-width: 90vw; - margin: 0 auto; - padding: 5vmin; - background-color: white; - border-radius: 2vmin; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); - text-align: center; -} - -.speeded-matching-instructions-container h1 { - color: #2c3e50; - margin-bottom: 3vmin; - font-size: 6vmin; -} - -.speeded-matching-instructions-container h2 { - color: #34495e; - margin-bottom: 2vmin; - font-size: 4vmin; -} - -.speeded-matching-instructions-container p { - color: #555; - line-height: 1.6; - margin-bottom: 2vmin; - font-size: 3vmin; -} - -.speeded-matching-instructions-container ul { - text-align: left; - max-width: 80%; - margin: 3vmin auto; -} - -.speeded-matching-instructions-container li { - margin-bottom: 1vmin; - color: #555; - font-size: 3vmin; -} - -.start-prompt { - font-weight: bold; - color: #2980b9 !important; - font-size: 4vmin !important; - margin-top: 4vmin !important; -} - -.speeded-matching-continue-button { - background-color: #3498db; - color: white; - border: none; - padding: 3vmin 6vmin; - font-size: 3vmin; - border-radius: 1vmin; - cursor: pointer; - margin: 2vmin; - transition: background-color 0.3s; - min-height: 48px; -} - -.speeded-matching-continue-button:hover { - background-color: #2980b9; -} - -/* Task instructions */ -.task-instructions { - margin-bottom: 3vmin; - text-align: center; -} - -.task-instructions p { - font-size: 4vmin; - color: #2c3e50; - font-weight: 600; - margin: 0; -} - -/* Target stimulus */ -.target-container { - margin-bottom: 4vmin; - text-align: center; -} - -.target-stimulus { - width: 30vmin; - height: 30vmin; - border: 4px solid #2c3e50; - border-radius: 2vmin; - background-color: white; - display: inline-flex; - align-items: center; - justify-content: center; - box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); - margin: 2vmin auto; -} - -.target-stimulus.flash { - animation: flash-border 0.5s ease-in-out 3; -} - -@keyframes flash-border { - 0%, 100% { - border-color: #2c3e50; - box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); - } - 50% { - border-color: #e74c3c; - box-shadow: 0 2vmin 4vmin rgba(231, 76, 60, 0.4); - } -} - -/* Button layout - flexible responsive grid */ -.jspsych-btn-group, -.jspsych-content .jspsych-btn-group, -#jspsych-html-button-response-btngroup { - display: flex !important; - flex-wrap: wrap !important; - gap: 2vmin !important; - max-width: 95vw !important; - margin: 3vmin auto !important; - justify-content: center !important; - align-items: center !important; -} - - -/* Choice buttons - dynamic sizing based on available space */ -.choice-option { - border: 2px solid #bdc3c7 !important; - border-radius: 2vmin !important; - background-color: white !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1) !important; - padding: 1.5vmin !important; - margin: 0 !important; - /* Dynamic sizing based on container width and number of items */ - flex: 1 1 auto !important; - min-height: 18vmin !important; - max-height: 35vmin !important; - height: calc(100vh / var(--num-choices, 4) - 2vmin) !important; - aspect-ratio: 1 !important; -} - -@media (max-width: 80vmin) { - .choice-option { - min-width: 12vmin !important; - max-width: 40vmin !important; - width: calc(80vw / var(--num-choices, 4) - 2vmin) !important; - } -} - -/* Responsive sizing for different screen sizes */ -@media (max-width: 130vmin) { - .choice-option { - min-width: 35vmin !important; - max-width: 40vmin !important; - width: calc(85vw / var(--num-choices, 4) - 2vmin) !important; - aspect-ratio: 1.1; /* Slightly wider for better readability */ - } -} - -.choice-option:hover { - border-color: #3498db !important; - transform: translateY(-0.5vmin) !important; - box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15) !important; -} - -.choice-option.flash-choices { - animation: flash-choices 0.5s ease-in-out 3 !important; -} - -@keyframes flash-choices { - 0%, 100% { - border-color: #bdc3c7 !important; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1) !important; - } - 50% { - border-color: #f39c12 !important; - box-shadow: 0 2vmin 3vmin rgba(243, 156, 18, 0.3) !important; - } -} - -/* SVG sizing */ -.target-stimulus svg { - width: 100% !important; - height: 100% !important; -} - -.choice-option svg { - width: 100% !important; - height: 100% !important; -} - -/* End screen */ -.speeded-matching-end-screen { - text-align: center; - max-width: 90vw; - margin: 0 auto; - padding: 5vmin; - background-color: white; - border-radius: 2vmin; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); -} - -.speeded-matching-end-screen h2 { - color: #27ae60; - font-size: 5vmin; - margin-bottom: 3vmin; -} - -.speeded-matching-end-screen p { - color: #555; - font-size: 3vmin; - line-height: 1.6; -} - -/* Fixation cross, set to invisible by default */ -.speeded-matching-fixation { - display: flex; - align-items: center; - justify-content: center; - font-size: 8vmin; - color: #ffffff; /* White color for invisibility */ - font-weight: bold; - min-height: 20vmin; -} - -/* Ready screen */ -.ready-screen { - text-align: center; - max-width: 90vw; - margin: 0 auto; - padding: 5vmin; - background-color: white; - border-radius: 2vmin; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); -} - -.ready-screen h2 { - color: #2c3e50; - font-size: 5vmin; - margin-bottom: 3vmin; -} - -.ready-screen p { - color: #555; - font-size: 3vmin; - line-height: 1.6; - margin-bottom: 4vmin; -} \ No newline at end of file diff --git a/packages/speeded-matching/src/test-items.ts b/packages/speeded-matching/src/test-items.ts deleted file mode 100644 index 084ef77e..00000000 --- a/packages/speeded-matching/src/test-items.ts +++ /dev/null @@ -1,116 +0,0 @@ -/* This file contains the animal SVGs used in the speeded matching task. - * Researchers can modify these SVGs to change the visual stimuli used in the experiment. - * Each SVG should be a complete SVG element as a string. - * - * To add new animal SVGs: - * 1. Go to https://www.svgrepo.com/collection/animal-portrait-vectors/ - * 2. Select an animal SVG - * 3. Click "Edit SVG" - * 4. Copy the SVG code - * 5. Add it to the array below - * - * Make sure all SVGs have similar dimensions for consistent display. - */ - -export const test_items = [ - // Cat SVG - ` - - - - - - - `, - - // Dog SVG - ` - - - - - - - `, - - // Bird SVG - ` - - - - - - `, - - // Fish SVG - ` - - - - - - `, - - // Rabbit SVG - ` - - - - - - - - `, - - // Elephant SVG - ` - - - - - - - `, - - // Lion SVG - ` - - - - - - - `, - - // Bear SVG - ` - - - - - - - `, - - // Frog SVG - ` - - - - - - - `, - - // Butterfly SVG - ` - - - - - - - - - ` -]; \ No newline at end of file diff --git a/packages/speeded-matching/src/text.ts b/packages/speeded-matching/src/text.ts deleted file mode 100644 index d9be197a..00000000 --- a/packages/speeded-matching/src/text.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* This file contains the configurable text used in the speeded matching timeline. - * Researchers can modify these texts to change the language or instructions. - */ - -export const trial_text = { - // Button texts - continue_button: "Continue", - start_button: "Start", - ready_button: "I'm Ready", - end_button: "End", - - // Task completion messages - task_complete_header: "Task Complete!", - task_complete_message: "Thank you for participating in the speeded matching task.", - - // Practice phase text - practice_header: "Practice Round", - practice_intro_message: "We'll now do a practice round to show you how the task works.", - practice_look_instruction: "Look at this picture", - practice_tap_instruction: "We are going to tap the matching picture below", - practice_complete_header: "Practice Complete!", - practice_complete_message: "Practice complete! Are you ready for the actual test?", - - // Main task instructions - main_task_prompt: "Click the picture that matches the target above.", - - // Fixation and inter-trial - fixation_cross: "+", - - // Feedback messages (optional) - correct_feedback: "Correct!", - incorrect_feedback: "Try again", - - // Timing messages - too_slow_message: "Please respond faster", -} - -/* - * This is an array of page objects that have configurable texts for the instruction pages - * before the actual trials. Researchers can modify these instructions to change the - * task description, add new instruction pages, or translate to different languages. - * - * Each page object can contain: - * - header: Main title (displays in

      ) - * - header2: Subtitle (displays in

      ) - * - description: Main description text (displays in

      ) - * - task_explanation: Detailed task explanation (displays in

      ) - * - performance_note: Performance tips (displays in

      ) - * - strategy_title: Strategy section title (displays in

      ) - * - strategy_intro: Introduction to strategy (displays in

      ) - * - strategy_points: Array of strategy bullet points (displays as

      • ) - * - start_prompt: Final instruction before starting (displays in

        ) - * - buttons: Array of button text options - * - button_html: Custom HTML for buttons (optional) - */ - -export const instruction_pages = [ - { - header: "Speeded Matching Task", - header2: "Visual Pattern Matching", - description: "In this task, you will see a target picture at the top of the screen and four choice pictures below.", - task_explanation: "Your job is to quickly identify which of the four pictures below matches the target picture at the top.", - performance_note: "Try to respond as quickly and accurately as possible. Speed and accuracy are both important.", - start_prompt: "Click continue to learn more about the task.", - buttons: ["Continue"], - }, - { - strategy_title: "Instructions", - strategy_intro: "For each trial:", - strategy_points: [ - "Look at the target picture at the top of the screen", - "Compare it with the four choice pictures below", - "Click on the picture that matches the target", - "Respond as quickly and accurately as possible", - "A brief outline will appear when you make your selection" - ], - start_prompt: "Click continue to start the practice round.", - buttons: ["Continue"], - } -]; \ No newline at end of file diff --git a/packages/speeded-matching/tsconfig.json b/packages/speeded-matching/tsconfig.json deleted file mode 100644 index 33655f70..00000000 --- a/packages/speeded-matching/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "@jspsych/config/tsconfig.contrib.json", - "compilerOptions": { - "baseUrl": "." - }, - "include": [ - "src", - "../cli/src/build.js" - ] -} \ No newline at end of file From f3c16c45043f04e0cf1fde3af70817016903e595 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Fri, 11 Jul 2025 16:19:34 -0400 Subject: [PATCH 28/37] feat: intelligent TTS, cleaned up audio config --- .../flanker-inhibitory/examples/index.html | 7 +- packages/flanker-inhibitory/implementPlan.md | 468 +++++++++++ packages/flanker-inhibitory/src/index.ts | 550 +++++++------ packages/flanker-inhibitory/src/stimuli.ts | 3 +- packages/flanker-inhibitory/src/styles.css | 773 ++++++++++-------- packages/flanker-inhibitory/src/text.ts | 83 +- 6 files changed, 1214 insertions(+), 670 deletions(-) create mode 100644 packages/flanker-inhibitory/implementPlan.md diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index 980fbd15..f3771147 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -49,18 +49,17 @@ fixation_duration: 500, // Duration of fixation cross in milliseconds // ===== TASK FLOW CONFIGURATION ===== - show_instructions: false, // Whether to show instruction screens - show_practice: false, // Whether to include practice trials + show_instructions: true, // Whether to show instruction screens + show_practice: true, // Whether to include practice trials num_practice: 3, // Number of practice trials num_trials: 20, // Number of main task trials // ===== TEXT-TO-SPEECH CONFIGURATION ===== enable_tts: true, // Enable text-to-speech functionality - tts_voice: '', // Voice name for TTS (empty uses default) tts_rate: 1.0, // Speech rate (0.1 to 10) tts_pitch: 1.0, // Speech pitch (0 to 2) tts_volume: 1.0, // Speech volume (0 to 1) - tts_lang: 'en-US', // Language code for TTS + tts_lang: 'ar-SA', // Language code for TTS speak_instructions: true, // Speak instruction page content speak_prompts: false, // Speak trial prompts speak_feedback: false, // Speak feedback messages diff --git a/packages/flanker-inhibitory/implementPlan.md b/packages/flanker-inhibitory/implementPlan.md new file mode 100644 index 00000000..4bb68634 --- /dev/null +++ b/packages/flanker-inhibitory/implementPlan.md @@ -0,0 +1,468 @@ +We are going to implement three things into this timeline project, some of which may already be implemented but we must adjust to match our files. Do not unnecessarily edit these implementations as they are standardized. For css ids and classes, comb the project's id's and classes to see if anything resembles what we have in the imported styles.css file (at the end of this file) and change it to match the imported file. We are trying to standardize styles, variables, and CSS here. If you make any notable or breaking changes make sure to alert me and describe them in detail. If there is a separate TTS object in text.ts or anything anywhere that you are unsure of what to do with just ask me. + +First, createInstructions() function that makes an instructions trial using jsPsychInstructions and supports TTS. This function will need the timeline to have a enable_tts parameter. If this does not exist in the project, add it, and then go to index.html and add it there as well and make sure it is set to true. Additionally, a text.ts file is required with specific variables: instruction_pages and trial_text. We have standardized these variable names so if there is similar objects or variables change them to match our format or just keep them separately. + +Implementation for createInstructions(): + +/** + * Function to provide text-to-speech functionality + * Researchers can modify speech settings like rate and volume + */ +function speakText(text: string) { + if ('speechSynthesis' in window) { + // Stop any ongoing speech + if ('speechSynthesis' in window) { + speechSynthesis.cancel(); + } + + // Wait a brief moment for cancel to take effect + setTimeout(() => { + // Create and speak the utterance + const utterance = new SpeechSynthesisUtterance(text); + utterance.rate = 0.8; // Slightly slower for clarity + utterance.volume = 0.8; + utterance.voice = speechSynthesis.getVoices()[0] || null; // Use first available voice + speechSynthesis.speak(utterance); + }, 100); + } +} + +/** + * Creates instruction pages with configurable text and TTS support + * Uses the jsPsych instructions plugin with simple HTML strings + */ +// Helper function to extract text from HTML for TTS +function extractTextFromHtml(htmlString: string): string { + // Use DOMParser for robust HTML to text extraction + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, 'text/html'); + return doc.body.textContent?.replace(/\s+/g, ' ').trim() || ''; +} + +function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false) { + // Closure variable to store the handler for cleanup + let handleButtonClick: ((event: Event) => void) | null = null; + + return { + type: jsPsychInstructions, + pages: instruction_pages_data.map(page => `

        ${page}

        `), + show_clickable_nav: true, + allow_keys: true, + key_forward: 'ArrowRight', + key_backward: 'ArrowLeft', + button_label_previous: trial_text.back_button, + button_label_next: trial_text.next_button, + on_start: function() { + speechSynthesis.cancel(); + }, + on_load: function() { + if (enable_tts) { + // Function to speak current page content + const speakCurrentPage = () => { + const instructionsContent = document.querySelector('.instructions-container'); + if (instructionsContent) { + const pageText = extractTextFromHtml(instructionsContent.innerHTML); + if (pageText.trim()) { + speakText(pageText); + } + } + }; + + // Use closure variable for handler + handleButtonClick = (event: Event) => { + const target = event.target as HTMLElement; + if (target && (target.id === 'jspsych-instructions-next' || target.id === 'jspsych-instructions-back')) { + speechSynthesis.cancel(); + setTimeout(speakCurrentPage, 200); + } + }; + + // Add single event listener to document + document.addEventListener('click', handleButtonClick); + + // Speak initial page + setTimeout(speakCurrentPage, 300); + } + }, + on_finish: function(data: any) { + speechSynthesis.cancel(); + // Clean up event listener using closure variable + if (handleButtonClick) { + document.removeEventListener('click', handleButtonClick); + handleButtonClick = null; + } + if (enable_tts) { + speechSynthesis.cancel(); + } + // Clean up navigation button listeners + if ((window as any).instructionsNavCleanup) { + (window as any).instructionsNavCleanup(); + delete (window as any).instructionsNavCleanup; + } + data.phase = 'instructions'; + } + }; +} + + +Implementation for text.ts file, do not import anything from trial_text except next_button and back_button, everything else only match to the variable in the existing text.ts file: + +/* This file contains the configurable text used in the speeded matching timeline. + * Researchers can modify these texts to change the language or instructions. + */ + +export const trial_text = { + // Button texts + continue_button: "Continue", + start_button: "Start", + ready_button: "I'm Ready", + end_button: "End", + // Instruction pages buttons text, these will always have arrows < and > + // these do not work right now due to CSS fixed position, might fix later + next_button: "", + back_button: "", + // Task completion messages + task_complete_header: "Task Complete!", + task_complete_message: "Thank you for participating in the speeded matching task.", + + // Practice phase text + practice_header: "Practice Round", + practice_intro_message: "We'll now do a practice round to show you how the task works.", + practice_look_instruction: "Look at this picture", + practice_tap_instruction: "Tap the matching picture below", + practice_complete_header: "Are you ready?", + practice_complete_message: "Practice complete! Ready for the full test?", + + // Main task instructions + main_task_prompt: "Tap the matching picture below", + + // Fixation and inter-trial + fixation_cross: "+", + + // Feedback messages (optional) + correct_feedback: "Correct!", + incorrect_feedback: "Try again", + + // Timing messages + too_slow_message: "Please respond faster", +} + +/* + * This is an array of HTML strings for instruction pages displayed before the actual trials. + * Researchers can modify these instructions to change the task description, add new instruction + * pages, or translate to different languages. + * + * Each string should contain valid HTML that will be displayed as an instruction page. + * You can add more pages by adding more strings to the array, or modify existing pages + * by editing the HTML content. + */ + +export const instruction_pages = [ + "You will see a picture at the top.", + "Below it, you will see four pictures.", + "Click on the picture that matches the one at the top.", + "Work quickly but carefully.", + "Let's practice first." +]; + + + +Now, we will implement a standardized styles.css. This file may have extraneous styles specific to the original timeline (such as choice-options and whatnot). Ignore these styles if they do not match anything in our current timeline project. If there are additional needed styles that are not in the imported styles, add them exactly as they are and do not change anything unneccesarily. Here is the imported styles.css: + +/* GENERAL STYLES */ + +h1, h2, h3 { + line-height: 1.6; + margin: 3vmin; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + max-width: 100%; + box-sizing: border-box; +} + +p { + margin: 5vmin 1vmin; + font-size: clamp(15px, 8vmin, 40px); + line-height: 1.6; + /* Prevent horizontal overflow */ + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + max-width: 100%; + box-sizing: border-box; +} + +/* Styles for any normal HTML button */ +.continue-button { + /* background-color: #3498db; + color: white; + border: none; */ + padding: clamp(15px, 6vmin, 35px) clamp(15px, 8vmin, 40px); + font-size: clamp(15px, 6vmin, 35px); + border-radius: 1vmin; + cursor: pointer; + margin: 2vmin; + margin-top: 10vh; + transition: background-color 0.3s; + min-height: 48px; + + /* Disable double-click zoom */ + touch-action: manipulation !important; + user-select: none !important; +} + +/* INSTRUCTIONS TRIAL STYLES */ + +.instructions-container { + max-width: 90vw; /* Limit width for better readability */ + margin: 0 auto; /* Center the container */ + padding: 2vmin; /* Add some padding */ + box-sizing: border-box; /* Include padding in width calculations */ + overflow-x: hidden; /* Prevent horizontal overflow */ +} + +/* Mobile-friendly instruction navigation buttons */ +.jspsych-instructions-nav { + display: flex; + gap: 25vw; + justify-content: center; + margin-top: auto; + padding: clamp(20px, 5vmin, 40px) !important; + min-height: clamp(20px, 50vmin, 250px); + align-items: flex-end; +} + +/* Instruction navigation button styles */ +.jspsych-instructions-nav .jspsych-btn { + background-color: #fff; + color: #000; + width: 20vmin; + height: 20vmin; + border: 3px solid #ccc; + border-radius: 50%; + font-size: clamp(15px, 7vmin, 35px); + cursor: pointer; + min-height: 100px; + min-width: 100px; + font-weight: 600; + + /* Disable double-click zoom */ + touch-action: manipulation !important; + user-select: none !important; +} + +.jspsych-instructions-nav .jspsych-btn:active { + background-color: #f0f0f0; + border-color: #999; +} + +.jspsych-instructions-nav .jspsych-btn:disabled { + visibility: hidden; /* Keeps the space but hides the button */ +} + +/* PRACTICE TRIAL STYLES */ + +/* pre-practice and post-practice round pages */ +.practice-instructions, .ready-screen { + max-width: 90vw; /* Limit width for better readability */ + margin: 0 auto; /* Center the container */ + padding: 2vmin; /* Add some padding */ + box-sizing: border-box; /* Include padding in width calculations */ + overflow-x: hidden; /* Prevent horizontal overflow */ + padding-bottom: 5vh; +} + +/* EXPERIMENT TRIAL STYLES */ + +/* Fixation cross (inter-trial), set to invisible by default */ +.fixation { + display: flex; + align-items: center; + justify-content: center; + font-size: 8vmin; + color: #ffffff; /* White color for invisibility */ + font-weight: bold; + min-height: 20vmin; +} +.trial-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + font-family: Arial, sans-serif; + padding: 2vmin; +} + +/* Task instructions in trial container, text on top. Usually there is none. */ +.task-instructions { + text-align: center; + p { + font-size: clamp(15px, 6vmin, 35px); + margin: 2vmin; + } +} + +/* Target stimulus*/ +.target-stimulus { + width: 30vmin; + height: 30vmin; + border: 3.5px solid #000000bd; + border-radius: 2vmin; + background-color: white; + display: inline-flex; + align-items: center; + justify-content: center; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); + margin: 2vmin; + svg { + width: 100%; + height: 100%; + } +} + +.target-stimulus.flash { + animation: flash-border 0.75s ease-in-out 3; +} + +@keyframes flash-border { + 0%, 100% { + border-color: #2c3e50; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); + } + 50% { + border-color: #e74c3c; + box-shadow: 0 2vmin 4vmin rgba(231, 76, 60, 0.4); + } +} + +/* Button layout - flexible responsive grid */ +#jspsych-html-button-response-btngroup { + display: flex; + flex-wrap: wrap; + gap: 2vmin; + max-width: 95vw; + margin: 3vmin auto; + justify-content: center; + align-items: center; +} + + +/* Choice buttons - dynamic sizing based on available space */ +.choice-option { + border: 2px solid #bdc3c7; + border-radius: 2vmin; + background-color: white !important; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer !important; + transition: all 0.3s ease; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); + padding: 1.5vmin; + margin: 2px 5px; + /* Disable double-click zoom */ + touch-action: manipulation !important; + user-select: none !important; + /* Fix the flexbox sizing */ + flex: 0 0 auto; /* Don't grow, don't shrink, use basis */ + flex-basis: clamp(20vmin, calc(163vmin / var(--num-choices, 4)), 90vmin); + height: clamp(20vmin, calc(80vh / var(--num-choices, 4)), 33vmin); + + /* Container-linked SVG scaling */ + svg { + width: 100%; + height: 100%; + } +} + +/* Hover effect for choice options, only visible on desktop */ +.choice-option:hover { + border-color: #3498db; + transform: translateY(-0.5vmin); + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); +} + +/* Flashing for practice round */ +.choice-option.flash-choices { + animation: flash-choices 1s ease-in-out infinite; +} + +@keyframes flash-choices { + 0%, 100% { + border-color: #bdc3c7; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); + } + 50% { + border-color: #f39c12; + box-shadow: 0 2vmin 3vmin rgba(243, 156, 18, 0.3); + } +} + +/* Styles for tablets */ +@media (max-width: 768px) { + .jspsych-instructions-nav { + position: fixed; + bottom: 30vmin; + left: 50%; + transform: translateX(-50%); + } + + /* Add padding to prevent content from going under fixed buttons */ + .instructions-container { + padding-bottom: 45vmin; /* 30vmin for button position + 5vmin safety */ + } + + /* Experiment-specific, adjusting sizes of images */ + .target-stimulus { + width: 35vmin; + height: 35vmin; + + } + .choice-option { + height: clamp(20vmin, calc(85vh / var(--num-choices, 4)), 30vh); + flex-basis: clamp(15vmin, calc(165vw / var(--num-choices, 4)), 43vw); + /* width: clamp(30vmin, calc(150vw / var(--num-choices, 4)), 43vmin); */ + /* aspect-ratio: 1;*/ + } + + +} + +/* Styles for phones to override tablet */ +@media (max-width: 480px) { + /* Smaller padding for phone to allow more text */ + .instructions-container { + padding-bottom: 40vmin; + } + .target-container { + margin-bottom: 4vmin; /* Adjusted for better spacing on small screens */ + } + /* Experiment-specific, adjusting sizes of images */ + .target-stimulus { + width: 45vmin; + height: 45vmin; + } +} + +/* Landscape mode adjustments */ +@media (orientation: landscape) { + .jspsych-instructions-nav { + bottom: 3vh; + gap: 30vw; + } + .target-container { + margin-bottom: 1vmin; + } + .target-stimulus { + width: 30vmin; + height: 30vmin; + border: 4px solid #000000bd; + } + .choice-option { + margin: 0; + flex-basis: clamp(15vmin, calc(85vw / var(--num-choices, 4)), 25vw) !important; + height: clamp(15vmin, calc(135vh / var(--num-choices, 4)), 35vmin) !important; + margin: -10px 2px; + } +} \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index 91185ff0..ec719107 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -1,154 +1,245 @@ import { JsPsych } from "jspsych"; import jsPsychHtmlButtonResponse from '@jspsych/plugin-html-button-response'; -import { trial_text, instruction_pages, tts_config, tts_text } from './text'; +import jsPsychInstructions from '@jspsych/plugin-instructions'; +import { trial_text, instruction_pages } from './text'; import { layered_stimuli, fish_only, arrow_only, custom_stimulus } from './stimuli'; // ============================================================================ -// TTS (TEXT-TO-SPEECH) UTILITIES +// STANDARDIZED CREATEINSTRUCTIONS FUNCTION // ============================================================================ -// Global TTS state management -let currentUtterance: SpeechSynthesisUtterance | null = null; -let speechQueue: string[] = []; -let speechEnabled = false; - -// Initialize TTS functionality -export function initializeTTS(config = tts_config) { - speechEnabled = 'speechSynthesis' in window; - - if (!speechEnabled) { - console.warn('Text-to-Speech not supported in this browser'); - return false; +// Global audio reference for stopping Google TTS +let currentGoogleAudio: HTMLAudioElement | null = null; + +/** + * Intelligent TTS with user preference support + * Tries user's preferred method first, then falls back to the alternative + */ +async function speakText(text: string, options: { lang?: string, volume?: number, method?: 'google' | 'system' } = {}) { + // Stop any current speech first and wait for it to stop + stopAllSpeech(); + + const preferredMethod = options.method || 'google'; + + // User prefers Google: try Google first, fallback to system + if (preferredMethod === 'google') { + try { + await speakWithGoogleTTS(text, options.lang || 'en'); + } catch (googleError) { + stopAllSpeech(); + await new Promise(resolve => setTimeout(resolve, 100)); + try { + await speakWithSystemTTS(text, options); + } catch (systemError) { + console.warn('🔊 TTS unavailable'); + } + } + return; } - // Stop any existing speech - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); + // User prefers System: try system first, fallback to Google + if (preferredMethod === 'system') { + try { + await speakWithSystemTTS(text, options); + } catch (systemError) { + stopAllSpeech(); + await new Promise(resolve => setTimeout(resolve, 100)); + try { + await speakWithGoogleTTS(text, options.lang || 'en'); + } catch (googleError) { + console.warn('🔊 TTS unavailable'); + } + } + return; } - - return true; -} - -// Get available voices -export function getAvailableVoices(): SpeechSynthesisVoice[] { - return speechSynthesis.getVoices(); } -// Create speech utterance with configuration -export function createUtterance(text: string, config = tts_config): SpeechSynthesisUtterance { - const utterance = new SpeechSynthesisUtterance(text); - - // Apply voice settings - utterance.rate = config.rate; - utterance.pitch = config.pitch; - utterance.volume = config.volume; - utterance.lang = config.lang; +/** + * Stop all speech including Google TTS audio - aggressively stops everything + */ +function stopAllSpeech() { + // Stop system TTS aggressively + if ('speechSynthesis' in window) { + speechSynthesis.cancel(); + speechSynthesis.pause(); + speechSynthesis.resume(); + speechSynthesis.cancel(); + } - // Set voice if specified - if (config.voice_name) { - const voices = getAvailableVoices(); - const selectedVoice = voices.find(voice => - voice.name.includes(config.voice_name) || voice.lang === config.lang - ); - if (selectedVoice) { - utterance.voice = selectedVoice; + // Stop Google TTS audio aggressively + if (currentGoogleAudio) { + try { + currentGoogleAudio.pause(); + currentGoogleAudio.currentTime = 0; // Reset to beginning + currentGoogleAudio.src = ''; // Clear source to stop loading + } catch (e) { + // Ignore errors, just ensure we clear the reference } + currentGoogleAudio = null; } - - return utterance; } -// Stop current speech -export function stopSpeech() { - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } - currentUtterance = null; - speechQueue = []; +/** + * Simple system TTS function + * Browser will automatically select the best voice for the specified language + */ +function speakWithSystemTTS(text: string, options: { rate?: number, volume?: number, pitch?: number, lang?: string } = {}) { + return new Promise((resolve, reject) => { + if ('speechSynthesis' in window) { + // Create and speak the utterance + const utterance = new SpeechSynthesisUtterance(text); + + // Apply options with defaults + utterance.rate = options.rate ?? 0.8; + utterance.volume = options.volume ?? 0.8; + utterance.pitch = options.pitch ?? 1.0; + + // Set language if provided (browser will pick best voice) + if (options.lang) { + utterance.lang = options.lang; + } + + // Add event listeners + utterance.onstart = () => resolve(); + utterance.onend = () => resolve(); + utterance.onerror = (e) => { + if (e.error === 'not-allowed' || e.error === 'synthesis-failed') { + reject(new Error(e.error)); // Reject on critical errors + } else { + resolve(); // Don't fail on minor errors since this is a fallback + } + }; + + speechSynthesis.speak(utterance); + } else { + reject(new Error('speechSynthesis not supported')); + } + }); } -// Speak text with delay and configuration -export function speakText(text: string, config = tts_config, delay = config.speech_delay): Promise { - return new Promise((resolve) => { - if (!speechEnabled || !text.trim()) { - resolve(); - return; - } - - setTimeout(() => { - // Stop any current speech - if (speechSynthesis.speaking) { - speechSynthesis.cancel(); - } +/** + * Defaultl TTS using Google Translate + * This works by creating an audio element that plays Google's TTS service + */ +function speakWithGoogleTTS(text: string, lang: string) { + return new Promise((resolve, reject) => { + try { + // Convert language code to simple 2-letter format for Google + const googleLang = lang ? lang.substring(0, 2).toLowerCase() : 'en'; + // Create Google Translate TTS URL + const encodedText = encodeURIComponent(text); + const googleTTSUrl = `https://translate.google.com/translate_tts?ie=UTF-8&tl=${googleLang}&client=tw-ob&q=${encodedText}`; - currentUtterance = createUtterance(text, config); + // Create and play audio + const audio = new Audio(googleTTSUrl); - currentUtterance.onend = () => { - currentUtterance = null; - resolve(); + // Store reference to current audio for stopping immediately + currentGoogleAudio = audio; + + audio.oncanplay = () => { + // Check if we were cancelled while loading + if (currentGoogleAudio !== audio) { + audio.pause(); + reject(new Error('Cancelled while loading')); + return; + } + audio.play().then(resolve).catch(reject); }; - currentUtterance.onerror = () => { - currentUtterance = null; + audio.onended = () => { + // Only clear if this is still the current audio + if (currentGoogleAudio === audio) { + currentGoogleAudio = null; + } resolve(); }; - speechSynthesis.speak(currentUtterance); - }, delay); + audio.onerror = (e) => { + // Always try to pause and clear, even on error + audio.pause(); + if (currentGoogleAudio === audio) { + currentGoogleAudio = null; + } + reject(new Error('Google TTS failed')); + }; + + // Load the audio + audio.load(); + + } catch (error) { + reject(error); + } }); } -// Speak instruction page content -export function speakInstructionPage(pageContent: string, config = tts_config): Promise { - if (!config.speak_instructions) { - return Promise.resolve(); - } - - // Remove HTML tags and clean up text for speech - const cleanText = pageContent - .replace(/<[^>]*>/g, ' ') // Remove HTML tags - .replace(/\s+/g, ' ') // Normalize any sequence of whitespace (including those left by the previous replace) to a single space - .trim(); - - return speakText(cleanText, config); +/** + * Creates instruction pages with configurable text and TTS support + * Uses the jsPsych instructions plugin with simple HTML strings + */ +// Helper function to extract text from HTML for TTS +function extractTextFromHtml(htmlString: string): string { + // Use DOMParser for robust HTML to text extraction + const parser = new DOMParser(); + const doc = parser.parseFromString(htmlString, 'text/html'); + return doc.body.textContent?.replace(/\s+/g, ' ').trim() || ''; } -// Speak trial prompt -export function speakTrialPrompt(config = tts_config): Promise { - if (!config.speak_prompts) { - return Promise.resolve(); - } - - return speakText(tts_text.trial_prompt_spoken, config); -} +function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false, ttsOptions = {}) { + // Closure variable to store the handler for cleanup + let handleButtonClick: ((event: Event) => void) | null = null; -// Speak feedback -export function speakFeedback(isCorrect: boolean, config = tts_config): Promise { - if (!config.speak_feedback) { - return Promise.resolve(); - } - - const feedbackText = isCorrect ? tts_text.correct_spoken : tts_text.incorrect_spoken; - return speakText(feedbackText, config); -} + return { + type: jsPsychInstructions, + pages: instruction_pages_data.map(page => `

        ${page}

        `), + show_clickable_nav: true, + allow_keys: true, + key_forward: 'ArrowRight', + key_backward: 'ArrowLeft', + button_label_previous: trial_text.back_button, + button_label_next: trial_text.next_button, + on_start: function() { + stopAllSpeech(); + }, + on_load: function() { + if (enable_tts) { + // Function to speak current page content + const speakCurrentPage = () => { + const instructionsContent = document.querySelector('.instructions-container'); + if (instructionsContent) { + const pageText = extractTextFromHtml(instructionsContent.innerHTML); + if (pageText.trim()) { + speakText(pageText, ttsOptions); + } + } + }; + + // Use closure variable for handler + handleButtonClick = (event: Event) => { + const target = event.target as HTMLElement; + if (target && (target.id === 'jspsych-instructions-next' || target.id === 'jspsych-instructions-back')) { + stopAllSpeech(); + // Wait longer to ensure speech has stopped before starting new speech + setTimeout(speakCurrentPage, 100); + } + }; -// Add keyboard listener for speech control -export function addSpeechControls(config = tts_config) { - if (!config.allow_skip) return undefined; - - const handleKeyPress = (event: KeyboardEvent) => { - if (event.code === 'Space' && speechSynthesis.speaking) { - event.preventDefault(); - stopSpeech(); + // Add single event listener to document + document.addEventListener('click', handleButtonClick); + + // Speak initial page + setTimeout(speakCurrentPage, 100); + } + }, + on_finish: function(data: any) { + stopAllSpeech(); + // Clean up event listener using closure variable + if (handleButtonClick) { + document.removeEventListener('click', handleButtonClick); + handleButtonClick = null; + } + data.phase = 'instructions'; } }; - - document.addEventListener('keydown', handleKeyPress); - - // Return cleanup function - return () => { - document.removeEventListener('keydown', handleKeyPress); - }; } // ============================================================================ @@ -345,17 +436,13 @@ export interface FlankerConfig { num_practice?: number; num_trials?: number; - // TTS Configuration + // Simplified TTS Configuration enable_tts?: boolean; // Enable text-to-speech functionality - tts_voice?: string; // Voice name to use for TTS - tts_rate?: number; // Speech rate (0.1 to 10) - tts_pitch?: number; // Speech pitch (0 to 2) - tts_volume?: number; // Speech volume (0 to 1) + tts_method?: 'google' | 'system'; // Preferred TTS method (default: 'google') + tts_rate?: number; // Speech rate (0.1 to 10, default: 0.8) + tts_pitch?: number; // Speech pitch (0 to 2, default: 1.0) + tts_volume?: number; // Speech volume (0 to 1, default: 0.8) tts_lang?: string; // Language code for TTS - speak_instructions?: boolean; // Speak instruction pages - speak_prompts?: boolean; // Speak trial prompts - speak_feedback?: boolean; // Speak feedback messages - auto_speak?: boolean; // Automatically start speaking when content loads } // ============================================================================ @@ -397,43 +484,22 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { show_practice = true, num_practice = 8, num_trials = 20, - - // TTS configuration enable_tts = false, - tts_voice = tts_config.voice_name, - tts_rate = tts_config.rate, - tts_pitch = tts_config.pitch, - tts_volume = tts_config.volume, - tts_lang = tts_config.lang, - speak_instructions = tts_config.speak_instructions, - speak_prompts = tts_config.speak_prompts, - speak_feedback = tts_config.speak_feedback, - auto_speak = tts_config.auto_speak_on_load, + tts_method = 'google', + tts_rate = 0.8, + tts_pitch = 1.0, + tts_volume = 0.8, + tts_lang, } = config; - // Initialize TTS if enabled - let ttsSettings = { ...tts_config }; - let speechCleanup: (() => void) | undefined; - - if (enable_tts) { - ttsSettings = { - ...tts_config, - voice_name: tts_voice, - rate: tts_rate, - pitch: tts_pitch, - volume: tts_volume, - lang: tts_lang, - speak_instructions, - speak_prompts, - speak_feedback, - auto_speak_on_load: auto_speak, - }; - - const ttsInitialized = initializeTTS(ttsSettings); - if (ttsInitialized) { - speechCleanup = addSpeechControls(ttsSettings); - } - } + // Create TTS options object + const ttsOptions = { + rate: tts_rate, + volume: tts_volume, + pitch: tts_pitch, + lang: tts_lang, + method: tts_method + }; // Determine which stimuli to use based on priority: // 1. svg override parameter (highest priority) @@ -454,43 +520,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { // Instructions if (show_instructions) { - instruction_pages.forEach((page, index) => { - timeline.push({ - type: jsPsychHtmlButtonResponse, - stimulus: ` -
        - ${page.header ? `

        ${page.header}

        ` : ''} - ${page.header2 ? `

        ${page.header2}

        ` : ''} - ${page.description ? `

        ${page.description}

        ` : ''} - ${page.task_explanation ? `

        ${page.task_explanation}

        ` : ''} - ${page.performance_note ? `

        ${page.performance_note}

        ` : ''} - ${page.strategy_title ? `

        ${page.strategy_title}

        ` : ''} - ${page.strategy_intro ? `

        ${page.strategy_intro}

        ` : ''} - ${page.strategy_points ? ` -
          - ${page.strategy_points.map(point => `
        • ${point}
        • `).join('')} -
        - ` : ''} - ${page.start_prompt ? `

        ${page.start_prompt}

        ` : ''} - ${enable_tts && ttsSettings.allow_skip ? `

        ${tts_text.speech_controls_note}

        ` : ''} -
        - `, - choices: page.buttons || [trial_text.continue_button], - data: { task: 'instructions', page: index + 1 }, - on_load: enable_tts ? function() { - if (index === 0) { - // Speak welcome message for first page - speakText(tts_text.welcome_spoken, ttsSettings); - } else if (index === 1) { - // Speak task explanation for second page - speakText(tts_text.task_explanation_spoken, ttsSettings); - } else if (index === 2) { - // Speak directions for third page - speakText(tts_text.directions_spoken, ttsSettings); - } - } : undefined - }); - }); + timeline.push(createInstructions(instruction_pages, enable_tts, ttsOptions)); } // Practice @@ -499,16 +529,22 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { timeline.push({ type: jsPsychHtmlButtonResponse, stimulus: ` -
        +

        ${trial_text.practice_header}

        ${trial_text.practice_intro_message}

        - ${enable_tts && ttsSettings.allow_skip ? `

        ${tts_text.speech_controls_note}

        ` : ''}
        `, choices: [trial_text.start_button], - data: { task: 'practice-intro' }, + button_html: (choice) => ``, + data: { + task: 'practice-intro', + phase: 'practice' + }, on_load: enable_tts ? function() { - speakText(tts_text.practice_start_spoken, ttsSettings); + const content = document.querySelector('.practice-instructions'); + if (content) { + speakText(extractTextFromHtml(content.innerHTML), ttsOptions); + } } : undefined }); @@ -531,7 +567,10 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { stimulus: `
        ${trial_text.fixation_cross}
        `, choices: [], trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'practice' } + data: { + task: 'fixation', + phase: 'practice' + } }; // Practice trial @@ -541,13 +580,16 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { const direction = jsPsych.evaluateTimelineVariable('direction'); const congruent = jsPsych.evaluateTimelineVariable('congruent'); return ` -
        -
        ${trial_text.main_task_prompt}
        - ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} +
        +
        +
        ${trial_text.main_task_prompt}
        + ${createPracticeStim(direction, congruent, stimuli, stimuli_amount)} +
        `; }, choices: [trial_text.left_button, trial_text.right_button], + button_html: (choice) => ``, data: { task: 'flanker', phase: 'practice', @@ -555,8 +597,9 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { congruent: jsPsych.timelineVariable('congruent') }, on_load: enable_tts ? function() { - if (ttsSettings.speak_prompts) { - speakTrialPrompt(ttsSettings); + const prompt = document.querySelector('.trial-prompt'); + if (prompt) { + speakText(extractTextFromHtml(prompt.innerHTML), ttsOptions); } } : undefined, on_finish: function(data: any) { @@ -575,11 +618,14 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { }, choices: [], trial_duration: 1000, - data: { task: 'feedback', phase: 'practice' }, + data: { + task: 'feedback', + phase: 'practice' + }, on_load: enable_tts ? function() { - if (ttsSettings.speak_feedback) { - const last_trial = jsPsych.data.get().last(1).values()[0]; - speakFeedback(last_trial.correct, ttsSettings); + const feedback = document.querySelector('.feedback'); + if (feedback) { + speakText(extractTextFromHtml(feedback.innerHTML), ttsOptions); } } : undefined }; @@ -600,18 +646,24 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { const performance = calculatePerformance(practice_data); return ` -
        +

        ${trial_text.practice_complete_header}

        ${trial_text.practice_complete_message}

        ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

        - ${enable_tts && ttsSettings.allow_skip ? `

        ${tts_text.speech_controls_note}

        ` : ''}
        `; }, choices: [trial_text.continue_button], - data: { task: 'practice-complete' }, + button_html: (choice) => ``, + data: { + task: 'practice-complete', + phase: 'practice' + }, on_load: enable_tts ? function() { - speakText(tts_text.practice_complete_spoken, ttsSettings); + const content = document.querySelector('.practice-instructions'); + if (content) { + speakText(extractTextFromHtml(content.innerHTML), ttsOptions); + } } : undefined }); } @@ -620,16 +672,22 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { timeline.push({ type: jsPsychHtmlButtonResponse, stimulus: ` -
        +

        ${trial_text.main_task_header}

        ${trial_text.main_task_intro}

        - ${enable_tts && ttsSettings.allow_skip ? `

        ${tts_text.speech_controls_note}

        ` : ''}
        `, choices: [trial_text.ready_button], - data: { task: 'main-intro' }, + button_html: (choice) => ``, + data: { + task: 'main-intro', + phase: 'main' + }, on_load: enable_tts ? function() { - speakText(tts_text.main_task_start_spoken, ttsSettings); + const content = document.querySelector('.ready-screen'); + if (content) { + speakText(extractTextFromHtml(content.innerHTML), ttsOptions); + } } : undefined }); @@ -651,7 +709,10 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { stimulus: `
        ${trial_text.fixation_cross}
        `, choices: [], trial_duration: fixation_duration, - data: { task: 'fixation', phase: 'main' } + data: { + task: 'fixation', + phase: 'main' + } }; // Main trial @@ -661,13 +722,16 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { const direction = jsPsych.evaluateTimelineVariable('direction'); const congruent = jsPsych.evaluateTimelineVariable('congruent'); return ` -
        -
        ${trial_text.main_task_prompt}
        - ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)} +
        +
        +
        ${trial_text.main_task_prompt}
        + ${createFlankerStim(direction, congruent, stimuli, stimuli_amount)} +
        `; }, choices: [trial_text.left_button, trial_text.right_button], + button_html: (choice) => ``, data: { task: 'flanker', phase: 'main', @@ -675,8 +739,9 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { congruent: jsPsych.timelineVariable('congruent') }, on_load: enable_tts ? function() { - if (ttsSettings.speak_prompts) { - speakTrialPrompt(ttsSettings); + const prompt = document.querySelector('.trial-prompt'); + if (prompt) { + speakText(extractTextFromHtml(prompt.innerHTML), ttsOptions); } } : undefined, on_finish: function(data: any) { @@ -701,7 +766,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { const performance = calculatePerformance(main_data); return ` -
        +

        ${trial_text.task_complete_header}

        ${trial_text.task_complete_message}

        @@ -709,23 +774,23 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) {

        ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

        ${trial_text.response_time_label} ${performance.mean_rt.toFixed(0)}ms

        - ${enable_tts && ttsSettings.allow_skip ? `

        ${tts_text.speech_controls_note}

        ` : ''}
        `; }, choices: [trial_text.end_button], - data: { task: 'complete' }, + button_html: (choice) => ``, + data: { + task: 'complete', + phase: 'completion' + }, on_load: enable_tts ? function() { - if (ttsSettings.speak_completion) { - speakText(tts_text.task_complete_spoken, ttsSettings); + const content = document.querySelector('.ready-screen'); + if (content) { + speakText(extractTextFromHtml(content.innerHTML), ttsOptions); } } : undefined, on_finish: function() { - // Cleanup speech controls when task is complete - if (speechCleanup) { - speechCleanup(); - } - stopSpeech(); + stopAllSpeech(); } }); @@ -749,20 +814,13 @@ export const utils = { calculatePerformance, createFlankerStim, createPracticeStim, - - // TTS utilities - initializeTTS, - speakText, - stopSpeech, - getAvailableVoices, - speakInstructionPage, - speakTrialPrompt, - speakFeedback, - addSpeechControls + createInstructions, + speakText }; -// Export text for external use -export { trial_text, instruction_pages, tts_config, tts_text } from './text'; +// Export text and createInstructions for external use +export { trial_text, instruction_pages } from './text'; +export { createInstructions }; // Default export export default { createTimeline, timelineUnits, utils }; \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/stimuli.ts b/packages/flanker-inhibitory/src/stimuli.ts index fed70288..a232937e 100644 --- a/packages/flanker-inhibitory/src/stimuli.ts +++ b/packages/flanker-inhibitory/src/stimuli.ts @@ -35,7 +35,8 @@ export const arrow_only = [ arrow_svg ]; -// Custom stimulus - you can add more layers +// Custom stimulus - you layer SVGs and add the directional svgs separately. +// If you add only one direction, it will be transformed to the other direction automatically. export const custom_stimulus = { left: [ '', diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css index 3ee61d23..813eb2f5 100644 --- a/packages/flanker-inhibitory/src/styles.css +++ b/packages/flanker-inhibitory/src/styles.css @@ -1,416 +1,471 @@ -/* Flanker Inhibitory Control Task Styles - Simple jsPsych approach */ - -/* Instructions */ -.flanker-instructions { - max-width: 90vw; - max-height: 85vh; - margin: 0 auto; - padding: 3vmin; - text-align: center; - line-height: 1.4; - background-color: white; - border-radius: 2vmin; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); - overflow-y: auto; +/* GENERAL STYLES */ + +h1, h2, h3 { + line-height: 1.6; + margin: 3vmin; + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + max-width: 100%; + box-sizing: border-box; +} + +p { + margin: 5vmin 1vmin; + font-size: clamp(15px, 8vmin, 40px); + line-height: 1.6; + /* Prevent horizontal overflow */ + word-wrap: break-word; + overflow-wrap: break-word; + hyphens: auto; + max-width: 100%; + box-sizing: border-box; +} + +/* Styles for any normal HTML button */ +.continue-button { + /* background-color: #3498db; + color: white; + border: none; */ + padding: clamp(15px, 6vmin, 35px) clamp(15px, 8vmin, 40px); + font-size: clamp(15px, 6vmin, 35px); + border-radius: 1vmin; + cursor: pointer; + margin: 2vmin; + margin-top: 10vh; + transition: background-color 0.3s; + min-height: 48px; + + /* Disable double-click zoom */ + touch-action: manipulation !important; + user-select: none !important; } -.flanker-instructions h1 { - color: #2c3e50; - text-align: center; - margin-bottom: 2vmin; - font-size: 4.5vmin; -} +/* INSTRUCTIONS TRIAL STYLES */ -.flanker-instructions h2 { - color: #34495e; - margin-top: 3vmin; - margin-bottom: 1.5vmin; - font-size: 3.5vmin; - text-align: center; +.instructions-container { + max-width: 90vw; /* Limit width for better readability */ + margin: 0 auto; /* Center the container */ + padding: 2vmin; /* Add some padding */ + box-sizing: border-box; /* Include padding in width calculations */ + overflow-x: hidden; /* Prevent horizontal overflow */ } -.flanker-instructions p { - color: #555; - margin-bottom: 1.5vmin; - font-size: 3vmin; - text-align: center; +/* Mobile-friendly instruction navigation buttons */ +.jspsych-instructions-nav { + display: flex; + gap: 25vw; + justify-content: center; + margin-top: auto; + padding: clamp(20px, 5vmin, 40px) !important; + min-height: clamp(20px, 50vmin, 250px); + align-items: flex-end; } -.flanker-instructions .performance-note { - background-color: #e8f4fd; - border-left: 4px solid #3498db; - padding: 1.5vmin 2vmin; - margin: 2vmin 0; - font-weight: 500; - border-radius: 1vmin; -} +/* Instruction navigation button styles */ +.jspsych-instructions-nav .jspsych-btn { + background-color: #fff; + color: #000; + width: 20vmin; + height: 20vmin; + border: 3px solid #ccc; + border-radius: 50%; + font-size: clamp(15px, 7vmin, 35px); + cursor: pointer; + min-height: 100px; + min-width: 100px; + font-weight: 600; -.flanker-instructions .start-prompt { - text-align: center; - font-weight: 600; - color: #2c3e50; - margin-top: 2vmin; - font-size: 3.5vmin; + /* Disable double-click zoom */ + touch-action: manipulation !important; + user-select: none !important; } -.flanker-instructions ul { - padding-left: 3vmin; - margin: 2vmin 0; +.jspsych-instructions-nav .jspsych-btn:active { + background-color: #f0f0f0; + border-color: #999; } -.flanker-instructions li { - margin: 1vmin 0; - font-size: 3vmin; - color: #555; - text-align: center; +.jspsych-instructions-nav .jspsych-btn:disabled { + visibility: hidden; /* Keeps the space but hides the button */ } -/* Performance summary */ -.performance-summary { - background-color: #f8f9fa; - border: 1px solid #dee2e6; - border-radius: 2vmin; - padding: 4vmin; - margin: 4vmin 0; - text-align: center; -} +/* PRACTICE TRIAL STYLES */ -.performance-summary h3 { - color: #495057; - margin-bottom: 3vmin; - text-align: center; - font-size: 4.5vmin; +/* pre-practice and post-practice round pages */ +.practice-instructions, .ready-screen { + max-width: 90vw; /* Limit width for better readability */ + margin: 0 auto; /* Center the container */ + padding: 2vmin; /* Add some padding */ + box-sizing: border-box; /* Include padding in width calculations */ + overflow-x: hidden; /* Prevent horizontal overflow */ + padding-bottom: 5vh; } -.performance-summary p { - margin: 2vmin 0; - font-size: 3.5vmin; - color: #6c757d; - text-align: center; +/* EXPERIMENT TRIAL STYLES */ + +/* Fixation cross (inter-trial), set to invisible by default */ +.fixation { + display: flex; + align-items: center; + justify-content: center; + font-size: 8vmin; + color: #ffffff; /* White color for invisibility */ + font-weight: bold; + min-height: 20vmin; +} +.trial-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-start; + font-family: Arial, sans-serif; + padding: 2vmin; } +/* Task instructions in trial container, text on top. Usually there is none. */ +.task-instructions { + text-align: center; + p { + font-size: clamp(15px, 6vmin, 35px); + margin: 2vmin; + } +} + +/* Target stimulus*/ +.target-stimulus { + width: 30vmin; + height: 30vmin; + border: 3.5px solid #000000bd; + border-radius: 2vmin; + background-color: white; + display: inline-flex; + align-items: center; + justify-content: center; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); + margin: 2vmin; + svg { + width: 100%; + height: 100%; + } +} + +.target-stimulus.flash { + animation: flash-border 0.75s ease-in-out 3; +} + +@keyframes flash-border { + 0%, 100% { + border-color: #2c3e50; + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); + } + 50% { + border-color: #e74c3c; + box-shadow: 0 2vmin 4vmin rgba(231, 76, 60, 0.4); + } +} + +/* Button layout - flexible responsive grid */ +#jspsych-html-button-response-btngroup { + display: flex; + flex-wrap: wrap; + gap: 2vmin; + max-width: 95vw; + margin: 3vmin auto; + justify-content: center; + align-items: center; +} + + +/* Choice buttons - dynamic sizing based on available space */ +.choice-option { + border: 2px solid #bdc3c7; + border-radius: 2vmin; + background-color: white !important; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer !important; + transition: all 0.3s ease; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); + padding: 1.5vmin; + margin: 2px 5px; + /* Disable double-click zoom */ + touch-action: manipulation !important; + user-select: none !important; + /* Fix the flexbox sizing */ + flex: 0 0 auto; /* Don't grow, don't shrink, use basis */ + flex-basis: clamp(20vmin, calc(163vmin / var(--num-choices, 4)), 90vmin); + height: clamp(20vmin, calc(80vh / var(--num-choices, 4)), 33vmin); + + /* Container-linked SVG scaling */ + svg { + width: 100%; + height: 100%; + } +} + +/* Hover effect for choice options, only visible on desktop */ +.choice-option:hover { + border-color: #3498db; + transform: translateY(-0.5vmin); + box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15); +} + +/* Flashing for practice round */ +.choice-option.flash-choices { + animation: flash-choices 1s ease-in-out infinite; +} + +@keyframes flash-choices { + 0%, 100% { + border-color: #bdc3c7; + box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.1); + } + 50% { + border-color: #f39c12; + box-shadow: 0 2vmin 3vmin rgba(243, 156, 18, 0.3); + } +} + +/* FLANKER-SPECIFIC STYLES - Keep existing functionality */ + /* Trial display */ .flanker-trial { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - min-height: 50vh; - padding: 3vmin; - gap: 4vmin; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 50vh; + padding: 3vmin; + gap: 4vmin; } .trial-prompt { - font-size: 3vmin; - font-weight: 200; - color: #2c3e50; - text-align: center; - max-width: 90vw; - line-height: normal; + font-size: 3vmin; + font-weight: 200; + color: #2c3e50; + text-align: center; + max-width: 90vw; + line-height: normal; } /* Flanker stimulus */ .flanker-stim { - display: flex; - align-items: center; - justify-content: center; - margin: 3vmin 0; - padding: 3vmin; - width: 100%; - max-width: 95vw; - gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); - - /* Dynamic sizing based on number of stimuli and screen space */ - --base-size: min( - calc(85vw / var(--stimuli-count, 5)), - calc(70vh / 1), - 20vmin - ); - --min-size: 4vmin; - --max-size: 25vmin; - --svg-size: max(var(--min-size), min(var(--max-size), var(--base-size))); - - /* Special handling for many stimuli (> 7) to ensure readability */ - --many-stimuli-adjustment: calc(max(0, var(--stimuli-count, 5) - 7) * 0.5vmin); - --adjusted-svg-size: max(calc(var(--svg-size) + var(--many-stimuli-adjustment)), var(--min-size)); + display: flex; + align-items: center; + justify-content: center; + margin: 3vmin 0; + padding: 3vmin; + width: 100%; + max-width: 95vw; + gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); + + /* Dynamic sizing based on number of stimuli and screen space */ + --base-size: min( + calc(85vw / var(--stimuli-count, 5)), + calc(70vh / 1), + 20vmin + ); + --min-size: 4vmin; + --max-size: 25vmin; + --svg-size: max(var(--min-size), min(var(--max-size), var(--base-size))); + + /* Special handling for many stimuli (> 7) to ensure readability */ + --many-stimuli-adjustment: calc(max(0, var(--stimuli-count, 5) - 7) * 0.5vmin); + --adjusted-svg-size: max(calc(var(--svg-size) + var(--many-stimuli-adjustment)), var(--min-size)); } .flanker-stim span { - display: flex; - align-items: center; - justify-content: center; - flex-shrink: 0; - vertical-align: middle; - line-height: 0; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + vertical-align: middle; + line-height: 0; } .flanker-stim svg { - width: var(--adjusted-svg-size, var(--svg-size)); - height: var(--adjusted-svg-size, var(--svg-size)); - display: block; + width: var(--adjusted-svg-size, var(--svg-size)); + height: var(--adjusted-svg-size, var(--svg-size)); + display: block; } /* Practice highlighting */ .flanker-stim.practice .center.highlighted { - background-color: #fff3cd; - border: 3px solid #ffc107; - border-radius: 50%; - padding: 1.5vmin; - animation: pulse 1.5s infinite; + background-color: #fff3cd; + border: 3px solid #ffc107; + border-radius: 50%; + padding: 1.5vmin; + animation: pulse 1.5s infinite; } @keyframes pulse { - 0% { - box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); - } - 70% { - box-shadow: 0 0 0 2vmin rgba(255, 193, 7, 0); - } - 100% { - box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); - } -} - -/* Fixation cross */ -.fixation { - display: flex; - align-items: center; - justify-content: center; - min-height: 20vh; - font-size: 6vmin; - font-weight: bold; - color: #2c3e50; + 0% { + box-shadow: 0 0 0 0 rgba(255, 193, 7, 0.7); + } + 70% { + box-shadow: 0 0 0 2vmin rgba(255, 193, 7, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(255, 193, 7, 0); + } } /* Feedback */ .feedback { - display: flex; - align-items: center; - justify-content: center; - min-height: 20vh; - font-size: 6vmin; - font-weight: bold; - text-align: center; - padding: 3vmin; + display: flex; + align-items: center; + justify-content: center; + min-height: 20vh; + font-size: 6vmin; + font-weight: bold; + text-align: center; + padding: 3vmin; } .feedback.correct { - color: #27ae60; + color: #27ae60; } .feedback.incorrect { - color: #e74c3c; + color: #e74c3c; } -/* ============================================================================ */ -/* JSPSYCH BUTTON OVERRIDES FOR MOBILE */ -/* ============================================================================ */ +/* Performance summary */ +.performance-summary { + background-color: #f8f9fa; + border: 1px solid #dee2e6; + border-radius: 2vmin; + padding: 4vmin; + margin: 4vmin 0; + text-align: center; +} -/* Button group layout */ -.jspsych-btn-group, -.jspsych-content .jspsych-btn-group, -#jspsych-html-button-response-btngroup { - display: flex !important; - flex-wrap: wrap !important; - gap: 3vmin !important; - max-width: 95vw !important; - margin: 4vmin auto !important; - justify-content: center !important; - align-items: center !important; -} - -/* All jsPsych buttons - mobile-friendly */ -.jspsych-btn { - border: 3px solid #3498db !important; - border-radius: 3vmin !important; - background-color: white !important; - color: #2c3e50 !important; - display: flex !important; - align-items: center !important; - justify-content: center !important; - cursor: pointer !important; - transition: all 0.3s ease !important; - box-shadow: 0 2vmin 3vmin rgba(0, 0, 0, 0.15) !important; - padding: 3vmin 6vmin !important; - margin: 1vmin !important; - font-family: inherit !important; - font-weight: bold !important; - font-size: 4vmin !important; - - /* Minimum touch target size */ - min-width: 15vmin !important; - min-height: 12vmin !important; -} - -/* Arrow response buttons - bigger for main task */ -.jspsych-btn:only-child { - /* Single buttons like Continue */ - min-width: 25vmin !important; - border-color: #27ae60 !important; - background-color: #2ecc71 !important; - color: white !important; -} - -/* For Left/Right arrow buttons (when there are exactly 2 buttons) */ -.jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - min-width: 20vmin !important; - min-height: 20vmin !important; - font-size: 8vmin !important; - flex: 1 1 auto !important; - max-width: 35vmin !important; -} - -/* Button hover states */ -.jspsych-btn:hover { - border-color: #2980b9 !important; - transform: translateY(-1vmin) !important; - box-shadow: 0 3vmin 4vmin rgba(0, 0, 0, 0.2) !important; - background-color: #ecf0f1 !important; -} - -.jspsych-btn:only-child:hover { - background-color: #27ae60 !important; - border-color: #229954 !important; - color: white !important; -} - -.jspsych-btn:active { - transform: translateY(0) !important; - box-shadow: 0 1vmin 2vmin rgba(0, 0, 0, 0.15) !important; -} - -/* ============================================================================ */ -/* RESPONSIVE DESIGN */ -/* ============================================================================ */ - -/* Small screens */ -@media (max-width: 480px) { - .flanker-stim { - gap: max(0.3vmin, calc(85vw / var(--stimuli-count, 5) * 0.08)); - padding: 2vmin; - - /* Tighter sizing for small screens */ - --base-size: min( - calc(80vw / var(--stimuli-count, 5)), - calc(50vh / 1), - 15vmin - ); - --min-size: 3vmin; - --max-size: 12vmin; - } - - .trial-prompt { - font-size: 4vmin; - } - - .fixation { - font-size: 10vmin; - min-height: 25vh; - } - - .jspsych-btn { - font-size: 3.5vmin !important; - } - - .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - font-size: 7vmin !important; - min-width: 18vmin !important; - min-height: 18vmin !important; - } -} - -/* Medium screens */ -@media (min-width: 481px) and (max-width: 768px) { - .flanker-stim { - gap: max(0.4vmin, calc(88vw / var(--stimuli-count, 5) * 0.09)); +.performance-summary h3 { + color: #495057; + margin-bottom: 3vmin; + text-align: center; + font-size: 4.5vmin; +} + +.performance-summary p { + margin: 2vmin 0; + font-size: 3.5vmin; + color: #6c757d; + text-align: center; +} + +/* Styles for tablets */ +@media (max-width: 768px) { + .jspsych-instructions-nav { + position: fixed; + bottom: 30vmin; + left: 50%; + transform: translateX(-50%); + } - /* Medium screen sizing */ - --base-size: min( - calc(82vw / var(--stimuli-count, 5)), - calc(60vh / 1), - 18vmin - ); - --min-size: 3.5vmin; - --max-size: 18vmin; - } - - .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - font-size: 8vmin !important; - min-width: 22vmin !important; - min-height: 22vmin !important; - } -} - -/* Large screens */ -@media (min-width: 769px) { - .flanker-stim { - gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); + /* Add padding to prevent content from going under fixed buttons */ + .instructions-container { + padding-bottom: 45vmin; /* 30vmin for button position + 5vmin safety */ + } + + /* Experiment-specific, adjusting sizes of images */ + .target-stimulus { + width: 35vmin; + height: 35vmin; + + } + .choice-option { + height: clamp(20vmin, calc(85vh / var(--num-choices, 4)), 30vh); + flex-basis: clamp(15vmin, calc(165vw / var(--num-choices, 4)), 43vw); + /* width: clamp(30vmin, calc(150vw / var(--num-choices, 4)), 43vmin); */ + /* aspect-ratio: 1;*/ + } + + /* Flanker-specific tablet adjustments */ + .flanker-stim { + gap: max(0.3vmin, calc(85vw / var(--stimuli-count, 5) * 0.08)); + padding: 2vmin; + + /* Tighter sizing for tablets */ + --base-size: min( + calc(80vw / var(--stimuli-count, 5)), + calc(50vh / 1), + 15vmin + ); + --min-size: 3vmin; + --max-size: 12vmin; + } +} + +/* Styles for phones to override tablet */ +@media (max-width: 480px) { + /* Smaller padding for phone to allow more text */ + .instructions-container { + padding-bottom: 40vmin; + } + .target-container { + margin-bottom: 4vmin; /* Adjusted for better spacing on small screens */ + } + /* Experiment-specific, adjusting sizes of images */ + .target-stimulus { + width: 45vmin; + height: 45vmin; + } + + /* Flanker-specific phone adjustments */ + .trial-prompt { + font-size: 4vmin; + } - /* Large screen sizing - more generous */ - --base-size: min( - calc(85vw / var(--stimuli-count, 5)), - calc(70vh / 1), - 22vmin - ); - --min-size: 4vmin; - --max-size: 30vmin; - } - - .jspsych-btn-group:has(.jspsych-btn:nth-child(2):last-child) .jspsych-btn { - font-size: 6vmin !important; - min-width: 15vmin !important; - min-height: 15vmin !important; - max-width: 20vmin !important; - } -} - -/* ============================================================================ */ -/* ACCESSIBILITY */ -/* ============================================================================ */ - -/* High contrast */ -@media (prefers-contrast: high) { - .flanker-stim svg { - filter: contrast(2); - } - - .performance-note { - background-color: #ffffff; - border-color: #000000; - color: #000000; - } - - .jspsych-btn { - border-color: #000000 !important; - background-color: #ffffff !important; - color: #000000 !important; - } -} - -/* Reduced motion */ -@media (prefers-reduced-motion: reduce) { - .flanker-stim.practice .center.highlighted { - animation: none; - } - - .jspsych-btn { - transition: none !important; - } -} - -/* Touch devices */ -@media (hover: none) and (pointer: coarse) { - .jspsych-btn:hover { - transform: none !important; - } - - .jspsych-btn:active { - background-color: #3498db !important; - color: white !important; - } - - .jspsych-btn:only-child:active { - background-color: #27ae60 !important; - color: white !important; - } + .flanker-stim { + gap: max(0.3vmin, calc(85vw / var(--stimuli-count, 5) * 0.08)); + padding: 2vmin; + + /* Tighter sizing for small screens */ + --base-size: min( + calc(80vw / var(--stimuli-count, 5)), + calc(50vh / 1), + 15vmin + ); + --min-size: 3vmin; + --max-size: 12vmin; + } +} + +/* Landscape mode adjustments */ +@media (orientation: landscape) { + .jspsych-instructions-nav { + bottom: 3vh; + gap: 30vw; + } + .target-container { + margin-bottom: 1vmin; + } + .target-stimulus { + width: 30vmin; + height: 30vmin; + border: 4px solid #000000bd; + } + .choice-option { + margin: 0; + flex-basis: clamp(15vmin, calc(85vw / var(--num-choices, 4)), 25vw) !important; + height: clamp(15vmin, calc(135vh / var(--num-choices, 4)), 35vmin) !important; + margin: -10px 2px; + } + + /* Flanker-specific landscape adjustments */ + .flanker-stim { + gap: max(0.4vmin, calc(88vw / var(--stimuli-count, 5) * 0.09)); + + /* Medium screen sizing */ + --base-size: min( + calc(82vw / var(--stimuli-count, 5)), + calc(60vh / 1), + 18vmin + ); + --min-size: 3.5vmin; + --max-size: 18vmin; + } } \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts index 4e998cc1..dada6b02 100644 --- a/packages/flanker-inhibitory/src/text.ts +++ b/packages/flanker-inhibitory/src/text.ts @@ -8,6 +8,10 @@ export const trial_text = { start_button: "Start", ready_button: "I'm Ready", end_button: "End", + // Instruction pages buttons text, these will always have arrows < and > + // these do not work right now due to CSS fixed position, might fix later + next_button: "", + back_button: "", practice_button: "Practice Again", left_button: "←", right_button: "→", @@ -36,9 +40,11 @@ export const trial_text = { // Fixation and inter-trial fixation_cross: "+", - // Feedback messages + // Feedback messages (optional) correct_feedback: "Correct!", - incorrect_feedback: "Incorrect", + incorrect_feedback: "Try again", + + // Timing messages too_slow_message: "Please respond faster", // Progress messages @@ -72,33 +78,6 @@ export const tts_config = { allow_skip: true, // Allow users to skip/stop speech }; -// Spoken versions of text (can be different from displayed text for clarity) -export const tts_text = { - // Instruction spoken versions - welcome_spoken: "Welcome to the Flanker Inhibitory Control Task. This is an attention and focus test.", - task_explanation_spoken: "You will see a group of fish swimming on the screen. Your job is to focus only on the middle fish and ignore the other fish around it.", - directions_spoken: "Look at which direction the middle fish is swimming and click the corresponding button. Click the left arrow if it's swimming left, or the right arrow if it's swimming right.", - - // Trial spoken versions - trial_prompt_spoken: "Look at the middle fish. Click the direction it's swimming.", - - // Feedback spoken versions - correct_spoken: "Correct", - incorrect_spoken: "Incorrect", - - // Practice spoken versions - practice_start_spoken: "Let's practice. Look at the middle fish and click the button that shows which way it's swimming.", - practice_complete_spoken: "Practice complete. Great job! Now you're ready for the real task.", - - // Main task spoken versions - main_task_start_spoken: "Now we'll start the real task. Remember to focus on the middle fish.", - task_complete_spoken: "Task complete. Thank you for participating.", - - // Controls - skip_speech_text: "Press space to skip speech", - speech_controls_note: "Speech can be skipped by pressing the space bar at any time.", -}; - /* * This is an array of page objects that have configurable texts for the instruction pages * before the actual trials. Researchers can modify these instructions to change the @@ -118,35 +97,19 @@ export const tts_text = { * - button_html: Custom HTML for buttons (optional) */ +/* + * This is an array of HTML strings for instruction pages displayed before the actual trials. + * Researchers can modify these instructions to change the task description, add new instruction + * pages, or translate to different languages. + * + * Each string should contain valid HTML that will be displayed as an instruction page. + * You can add more pages by adding more strings to the array, or modify existing pages + * by editing the HTML content. + */ + export const instruction_pages = [ - { - header: "Flanker Inhibitory Control Task", - header2: "Attention and Focus Test", - description: "In this task, you will see a group of fish swimming on the screen.", - task_explanation: "Your job is to focus only on the middle fish and ignore the other fish around it.", - performance_note: "Look at which direction the middle fish is swimming and click the corresponding button.", - start_prompt: "Click continue to learn more about the task.", - buttons: ["Continue"], - }, - { - strategy_title: "Instructions", - strategy_intro: "For each trial:", - strategy_points: [ - "Look at the middle fish only - ignore the other fish", - "See which direction the middle fish is swimming", - "Click the ← button if it's swimming left", - "Click the → button if it's swimming right", - "Try to respond as quickly and accurately as possible" - ], - start_prompt: "The other fish around the middle fish might be swimming in different directions - don't let them distract you!", - buttons: ["Continue"], - }, - { - header2: "Button Instructions", - description: "Use these buttons to respond:", - task_explanation: "← Left Button = Middle fish swimming left
        → Right Button = Middle fish swimming right", - performance_note: "Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions.", - start_prompt: "Click continue to start the practice round.", - buttons: ["Continue"], - } -]; \ No newline at end of file +"حبيبي حبيبي حبيبي", "حبيبي حبيبي حبيبي", "Look at the middle fish only - ignore the other fish. See which direction the middle fish is swimming. Click the ← button if it's swimming left, click the → button if it's swimming right.", + "Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions TWOO. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions THREE. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FOUR. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FIVE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SIX Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SEVEN Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. EIGHT Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. NINE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. TEN. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. ELEVENTry to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions TWOO. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions THREE. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FOUR. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FIVE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SIX Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SEVEN Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. EIGHT Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. NINE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. TEN. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. ELEVENTry to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions TWOO. Try ", + "Click continue to start the practice round." +]; + From d3d89c367905a4947df887918a6d8928c3910db1 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Fri, 11 Jul 2025 16:31:49 -0400 Subject: [PATCH 29/37] feat: enhanced fallback system for TTS --- packages/flanker-inhibitory/src/index.ts | 57 ++++++++++++------------ 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index ec719107..3078d3e9 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -4,16 +4,12 @@ import jsPsychInstructions from '@jspsych/plugin-instructions'; import { trial_text, instruction_pages } from './text'; import { layered_stimuli, fish_only, arrow_only, custom_stimulus } from './stimuli'; -// ============================================================================ -// STANDARDIZED CREATEINSTRUCTIONS FUNCTION -// ============================================================================ - // Global audio reference for stopping Google TTS let currentGoogleAudio: HTMLAudioElement | null = null; /** * Intelligent TTS with user preference support - * Tries user's preferred method first, then falls back to the alternative + * Tries user's preferred method first, then the other method as fallback */ async function speakText(text: string, options: { lang?: string, volume?: number, method?: 'google' | 'system' } = {}) { // Stop any current speech first and wait for it to stop @@ -21,36 +17,39 @@ async function speakText(text: string, options: { lang?: string, volume?: number const preferredMethod = options.method || 'google'; - // User prefers Google: try Google first, fallback to system - if (preferredMethod === 'google') { - try { + // Try preferred method first + try { + if (preferredMethod === 'google') { await speakWithGoogleTTS(text, options.lang || 'en'); - } catch (googleError) { - stopAllSpeech(); - await new Promise(resolve => setTimeout(resolve, 100)); - try { - await speakWithSystemTTS(text, options); - } catch (systemError) { - console.warn('🔊 TTS unavailable'); - } + return; + } else { + await speakWithSystemTTS(text, options); + return; } + } catch (preferredSpeechError) { + // Preferred method failed, continue to try all methods + } + + // Try Google TTS regardless + stopAllSpeech(); + await new Promise(resolve => setTimeout(resolve, 100)); + + try { + await speakWithGoogleTTS(text, options.lang || 'en'); return; + } catch (googleError) { + // Google failed, continue to system } - // User prefers System: try system first, fallback to Google - if (preferredMethod === 'system') { - try { - await speakWithSystemTTS(text, options); - } catch (systemError) { - stopAllSpeech(); - await new Promise(resolve => setTimeout(resolve, 100)); - try { - await speakWithGoogleTTS(text, options.lang || 'en'); - } catch (googleError) { - console.warn('🔊 TTS unavailable'); - } - } + // Try system TTS as final fallback + stopAllSpeech(); + await new Promise(resolve => setTimeout(resolve, 100)); + + try { + await speakWithSystemTTS(text, options); return; + } catch (systemError) { + console.warn('🔊 TTS unavailable'); } } From 3a23552ac773474319576cea4470dfbe0a835675 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Sun, 13 Jul 2025 23:22:13 -0400 Subject: [PATCH 30/37] fix: removed non-functional config options from index.html --- .../flanker-inhibitory/examples/index.html | 2 +- packages/flanker-inhibitory/implementPlan.md | 238 +++++++++++++++--- 2 files changed, 200 insertions(+), 40 deletions(-) diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index f3771147..d7df79ed 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -60,7 +60,7 @@ tts_pitch: 1.0, // Speech pitch (0 to 2) tts_volume: 1.0, // Speech volume (0 to 1) tts_lang: 'ar-SA', // Language code for TTS - speak_instructions: true, // Speak instruction page content + speak_instructions: false, // Speak instruction page content speak_prompts: false, // Speak trial prompts speak_feedback: false, // Speak feedback messages auto_speak: true, // Auto-start speaking when content loads diff --git a/packages/flanker-inhibitory/implementPlan.md b/packages/flanker-inhibitory/implementPlan.md index 4bb68634..0bd3957b 100644 --- a/packages/flanker-inhibitory/implementPlan.md +++ b/packages/flanker-inhibitory/implementPlan.md @@ -1,30 +1,196 @@ -We are going to implement three things into this timeline project, some of which may already be implemented but we must adjust to match our files. Do not unnecessarily edit these implementations as they are standardized. For css ids and classes, comb the project's id's and classes to see if anything resembles what we have in the imported styles.css file (at the end of this file) and change it to match the imported file. We are trying to standardize styles, variables, and CSS here. If you make any notable or breaking changes make sure to alert me and describe them in detail. If there is a separate TTS object in text.ts or anything anywhere that you are unsure of what to do with just ask me. +We are going to implement four things (speaktext function, createinstructions function and text.ts, styles.css) into this timeline project, some of which may already be implemented but we must edit them to match our desired standardized outcome and current project. Do not unnecessarily edit these implementations as they are standardized across multiple projects, only edit if necessary to add some project-specific functionality or style. For css ids and classes, comb the project's id's and classes to see if anything resembles what we have in the imported styles.css file (at the end of this file) and change it to match the imported file. We are trying to standardize styles, variables, and CSS here. If you make any notable or breaking changes make sure to alert me and describe them in detail. If there is a separate TTS object in text.ts or anything anywhere that you are unsure of what to do with just ask me, but with separate TTS objects we probably want them replaced and fully automated using our speakText function. -First, createInstructions() function that makes an instructions trial using jsPsychInstructions and supports TTS. This function will need the timeline to have a enable_tts parameter. If this does not exist in the project, add it, and then go to index.html and add it there as well and make sure it is set to true. Additionally, a text.ts file is required with specific variables: instruction_pages and trial_text. We have standardized these variable names so if there is similar objects or variables change them to match our format or just keep them separately. +First, createInstructions() function that makes an instructions trial using jsPsychInstructions and supports TTS. This function will need the timeline to have a few parameters added to the config: -Implementation for createInstructions(): + // ===== TEXT-TO-SPEECH CONFIGURATION ===== + enable_tts: true, // Enable text-to-speech functionality + tts_method: 'google', // Preferred TTS method: 'google', 'system' + tts_rate: 1.0, // Speech rate (0.1 to 10) + tts_pitch: 1.0, // Speech pitch (0 to 2) + tts_volume: 1.0, // Speech volume (0 to 1) + tts_lang: 'ar-SA', // Language code for TTS +}; + +in index.ts: + // TTS Configuration + enable_tts?: boolean; // Enable text-to-speech functionality + tts_method?: 'google' | 'system'; // Preferred TTS method (default: 'google') + tts_rate?: number; // Speech rate (0.1 to 10, default: 0.8) + tts_pitch?: number; // Speech pitch (0 to 2, default: 1.0) + tts_volume?: number; // Speech volume (0 to 1, default: 0.8) + tts_lang?: string; // Language code for TTS + + If this does not exist in the project, add it, and then go to index.html and add it there as well and make sure it is set by default to the above config in index.ts and index.html. Additionally, a text.ts file is required with specific variables: instruction_pages and trial_text. We have standardized these variable names so if there is similar objects or variables change them to match our format or just create new array instruction_pages and object trial_text. + +If there is an existing speakText() function or any other speaking function, just replace it with what we have and make sure to go through every trial object and rewrite the speakText call to be similar to the one in our createInstructions. + +Implementation for speakText and createInstructions(): + +// Global audio reference for stopping Google TTS +let currentGoogleAudio: HTMLAudioElement | null = null; + +/** + * Intelligent TTS with user preference support + * Tries user's preferred method first, then the other method as fallback + */ +async function speakText(text: string, options: { lang?: string, volume?: number, method?: 'google' | 'system' } = {}) { + // Stop any current speech first and wait for it to stop + stopAllSpeech(); + + const preferredMethod = options.method || 'google'; + + // Try preferred method first + try { + if (preferredMethod === 'google') { + await speakWithGoogleTTS(text, options.lang || 'en'); + return; + } else { + await speakWithSystemTTS(text, options); + return; + } + } catch (preferredSpeechError) { + // Preferred method failed, continue to try all methods + } + + // Try Google TTS regardless + stopAllSpeech(); + await new Promise(resolve => setTimeout(resolve, 100)); + + try { + await speakWithGoogleTTS(text, options.lang || 'en'); + return; + } catch (googleError) { + // Google failed, continue to system + } + + // Try system TTS as final fallback + stopAllSpeech(); + await new Promise(resolve => setTimeout(resolve, 100)); + + try { + await speakWithSystemTTS(text, options); + return; + } catch (systemError) { + console.warn('🔊 TTS unavailable'); + } +} /** - * Function to provide text-to-speech functionality - * Researchers can modify speech settings like rate and volume + * Stop all speech including Google TTS audio - aggressively stops everything */ -function speakText(text: string) { +function stopAllSpeech() { + // Stop system TTS aggressively if ('speechSynthesis' in window) { - // Stop any ongoing speech - if ('speechSynthesis' in window) { speechSynthesis.cancel(); + speechSynthesis.pause(); + speechSynthesis.resume(); + speechSynthesis.cancel(); + } + + // Stop Google TTS audio aggressively + if (currentGoogleAudio) { + try { + currentGoogleAudio.pause(); + currentGoogleAudio.currentTime = 0; // Reset to beginning + currentGoogleAudio.src = ''; // Clear source to stop loading + } catch (e) { + // Ignore errors, just ensure we clear the reference } - - // Wait a brief moment for cancel to take effect - setTimeout(() => { + currentGoogleAudio = null; + } +} + +/** + * Simple system TTS function + * Browser will automatically select the best voice for the specified language + */ +function speakWithSystemTTS(text: string, options: { rate?: number, volume?: number, pitch?: number, lang?: string } = {}) { + return new Promise((resolve, reject) => { + if ('speechSynthesis' in window) { // Create and speak the utterance const utterance = new SpeechSynthesisUtterance(text); - utterance.rate = 0.8; // Slightly slower for clarity - utterance.volume = 0.8; - utterance.voice = speechSynthesis.getVoices()[0] || null; // Use first available voice + + // Apply options with defaults + utterance.rate = options.rate ?? 0.8; + utterance.volume = options.volume ?? 0.8; + utterance.pitch = options.pitch ?? 1.0; + + // Set language if provided (browser will pick best voice) + if (options.lang) { + utterance.lang = options.lang; + } + + // Add event listeners + utterance.onstart = () => resolve(); + utterance.onend = () => resolve(); + utterance.onerror = (e) => { + if (e.error === 'not-allowed' || e.error === 'synthesis-failed') { + reject(new Error(e.error)); // Reject on critical errors + } else { + resolve(); // Don't fail on minor errors since this is a fallback + } + }; + speechSynthesis.speak(utterance); - }, 100); - } + } else { + reject(new Error('speechSynthesis not supported')); + } + }); +} + +/** + * Defaultl TTS using Google Translate + * This works by creating an audio element that plays Google's TTS service + */ +function speakWithGoogleTTS(text: string, lang: string) { + return new Promise((resolve, reject) => { + try { + // Convert language code to simple 2-letter format for Google + const googleLang = lang ? lang.substring(0, 2).toLowerCase() : 'en'; + // Create Google Translate TTS URL + const encodedText = encodeURIComponent(text); + const googleTTSUrl = `https://translate.google.com/translate_tts?ie=UTF-8&tl=${googleLang}&client=tw-ob&q=${encodedText}`; + + // Create and play audio + const audio = new Audio(googleTTSUrl); + + // Store reference to current audio for stopping immediately + currentGoogleAudio = audio; + + audio.oncanplay = () => { + // Check if we were cancelled while loading + if (currentGoogleAudio !== audio) { + audio.pause(); + reject(new Error('Cancelled while loading')); + return; + } + audio.play().then(resolve).catch(reject); + }; + + audio.onended = () => { + // Only clear if this is still the current audio + if (currentGoogleAudio === audio) { + currentGoogleAudio = null; + } + resolve(); + }; + + audio.onerror = (e) => { + // Always try to pause and clear, even on error + audio.pause(); + if (currentGoogleAudio === audio) { + currentGoogleAudio = null; + } + reject(new Error('Google TTS failed')); + }; + + // Load the audio + audio.load(); + + } catch (error) { + reject(error); + } + }); } /** @@ -39,7 +205,7 @@ function extractTextFromHtml(htmlString: string): string { return doc.body.textContent?.replace(/\s+/g, ' ').trim() || ''; } -function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false) { +function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false, ttsOptions = {}) { // Closure variable to store the handler for cleanup let handleButtonClick: ((event: Event) => void) | null = null; @@ -53,7 +219,7 @@ function createInstructions(instruction_pages_data = instruction_pages, enable_t button_label_previous: trial_text.back_button, button_label_next: trial_text.next_button, on_start: function() { - speechSynthesis.cancel(); + stopAllSpeech(); }, on_load: function() { if (enable_tts) { @@ -63,7 +229,7 @@ function createInstructions(instruction_pages_data = instruction_pages, enable_t if (instructionsContent) { const pageText = extractTextFromHtml(instructionsContent.innerHTML); if (pageText.trim()) { - speakText(pageText); + speakText(pageText, ttsOptions); } } }; @@ -72,8 +238,9 @@ function createInstructions(instruction_pages_data = instruction_pages, enable_t handleButtonClick = (event: Event) => { const target = event.target as HTMLElement; if (target && (target.id === 'jspsych-instructions-next' || target.id === 'jspsych-instructions-back')) { - speechSynthesis.cancel(); - setTimeout(speakCurrentPage, 200); + stopAllSpeech(); + // Wait longer to ensure speech has stopped before starting new speech + setTimeout(speakCurrentPage, 100); } }; @@ -81,46 +248,36 @@ function createInstructions(instruction_pages_data = instruction_pages, enable_t document.addEventListener('click', handleButtonClick); // Speak initial page - setTimeout(speakCurrentPage, 300); + setTimeout(speakCurrentPage, 100); } }, on_finish: function(data: any) { - speechSynthesis.cancel(); + stopAllSpeech(); // Clean up event listener using closure variable if (handleButtonClick) { document.removeEventListener('click', handleButtonClick); handleButtonClick = null; } - if (enable_tts) { - speechSynthesis.cancel(); - } - // Clean up navigation button listeners - if ((window as any).instructionsNavCleanup) { - (window as any).instructionsNavCleanup(); - delete (window as any).instructionsNavCleanup; - } data.phase = 'instructions'; } }; } -Implementation for text.ts file, do not import anything from trial_text except next_button and back_button, everything else only match to the variable in the existing text.ts file: +Implementation for text.ts file, you can add text placeholders and objects specific to the project we are working on. You should probably go through the index.ts file and make sure every piece of text is customizable through text.ts /* This file contains the configurable text used in the speeded matching timeline. * Researchers can modify these texts to change the language or instructions. */ export const trial_text = { - // Button texts - continue_button: "Continue", - start_button: "Start", - ready_button: "I'm Ready", - end_button: "End", // Instruction pages buttons text, these will always have arrows < and > // these do not work right now due to CSS fixed position, might fix later next_button: "", back_button: "", + + (MESSAGE TO CLAUDE: THE FOLLOWING TEXT ITEMS MAY OR MAY NOT EXIST IN THIS PROJECT. IF THEY DO NOT EXIST OR MATCH ANYTHING SIMILAR, REMOVE THEM. OTHERWISE KEEP THEM WITH THE SAME NAMING SCHEME.) + // Task completion messages task_complete_header: "Task Complete!", task_complete_message: "Thank you for participating in the speeded matching task.", @@ -156,7 +313,7 @@ export const trial_text = { * You can add more pages by adding more strings to the array, or modify existing pages * by editing the HTML content. */ - +(MESSAGE TO CLAUDE: WHATEVER THE CONTENT OF EACH PAGE IS FOR THE CURRENT PROJECT, IF ITS TOO LONG OR INCLUDES UNNECESSARY INFO TO THE PARTICIPANT (SUCH AS THE EXPERIMENTS NAME OR ANYTHING OTHER THAN WHAT THE PARTICIPANT NEEDS TO KNOW TO DO THE EXPERIMENT) THEN SIMPLIFY. APPROACH TO REWRITE INSTRUCTIONS: MORE PAGES WITH SHORT TEXT RATHER THAN LONG PARAGRAPHS OR BULLET POINTS.) export const instruction_pages = [ "You will see a picture at the top.", "Below it, you will see four pictures.", @@ -167,7 +324,10 @@ export const instruction_pages = [ -Now, we will implement a standardized styles.css. This file may have extraneous styles specific to the original timeline (such as choice-options and whatnot). Ignore these styles if they do not match anything in our current timeline project. If there are additional needed styles that are not in the imported styles, add them exactly as they are and do not change anything unneccesarily. Here is the imported styles.css: +Now, we will implement a standardized styles.css. This file may have extraneous styles not specific to our current timeline project (such as choice-options and whatnot). Remove these styles if they do not match anything in our current timeline project. If there are additional styles in the project that are not in this styles.css file, add them exactly as they are and do not change anything unneccesarily. Additionally, if there are any class names for instructions or trials that are here but not in the index.ts trials, you should probably add them to that trial's HTML. For example, for any html button response plugin usage, you should add button_html: (choice) => ``, + so that the styles will apply to the buttons. Do similar things for instructions container or anything that is in our styles.css but not in the index.ts if applicable. Be careful not to mess up things, if unsure just prompt me to decide on things even. Oh and if you change any HTML you should probably check the speakText functions again on what HTML and CSS containers they are querying and make sure you change those if necessary too. + +Here is the imported styles.css: /* GENERAL STYLES */ From 2022c9e1b75c7b65ad498790f0bc3a85b75e10ca Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 14 Jul 2025 16:00:56 -0400 Subject: [PATCH 31/37] fix: buttons too big on phone landscape mode --- .../flanker-inhibitory/examples/index.html | 8 +-- packages/flanker-inhibitory/src/styles.css | 58 ++++++++++--------- packages/flanker-inhibitory/src/text.ts | 11 ++-- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index d7df79ed..af7d459b 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -36,7 +36,7 @@ // Configure the task with ALL available parameters set to their defaults const config = { // ===== STIMULUS CONFIGURATION ===== - stimuli_type: 'layered', // 'fish', 'arrow', 'custom', or 'layered' (check stimuli.ts for details) + stimuli_type: 'fish', // 'fish', 'arrow', 'custom', or 'layered' (check stimuli.ts for details) // svg: [ // '', // '' + @@ -59,11 +59,7 @@ tts_rate: 1.0, // Speech rate (0.1 to 10) tts_pitch: 1.0, // Speech pitch (0 to 2) tts_volume: 1.0, // Speech volume (0 to 1) - tts_lang: 'ar-SA', // Language code for TTS - speak_instructions: false, // Speak instruction page content - speak_prompts: false, // Speak trial prompts - speak_feedback: false, // Speak feedback messages - auto_speak: true, // Auto-start speaking when content loads + tts_lang: 'en-US', // Language code for TTS }; // Create the timeline diff --git a/packages/flanker-inhibitory/src/styles.css b/packages/flanker-inhibitory/src/styles.css index 813eb2f5..9528cfb6 100644 --- a/packages/flanker-inhibitory/src/styles.css +++ b/packages/flanker-inhibitory/src/styles.css @@ -20,22 +20,25 @@ p { hyphens: auto; max-width: 100%; box-sizing: border-box; + text-align: center; } /* Styles for any normal HTML button */ .continue-button { - /* background-color: #3498db; - color: white; - border: none; */ - padding: clamp(15px, 6vmin, 35px) clamp(15px, 8vmin, 40px); - font-size: clamp(15px, 6vmin, 35px); - border-radius: 1vmin; + padding: clamp(20px, 8vh, 50px) clamp(25px, 12vmin, 60px); /* Bigger padding */ + @media (orientation: landscape) and (max-width: 1024px) { + padding: clamp(0px, 3vh, 22px); /* Smaller padding for landscape */ + } + font-size: clamp(18px, 8vmin, 45px); /* Bigger font */ + border: 3px solid #00000061; + border-radius: 5%; cursor: pointer; margin: 2vmin; margin-top: 10vh; transition: background-color 0.3s; - min-height: 48px; - + min-height: 60px; /* Bigger minimum height */ + flex: 1; /* Buttons will grow to fill available space */ + max-width: min(45vw, 400px); /* Responsive max-width */ /* Disable double-click zoom */ touch-action: manipulation !important; user-select: none !important; @@ -120,7 +123,7 @@ p { align-items: center; justify-content: flex-start; font-family: Arial, sans-serif; - padding: 2vmin; + padding: 2vmin 0; /* Add vertical padding */ } /* Task instructions in trial container, text on top. Usually there is none. */ @@ -169,9 +172,9 @@ p { #jspsych-html-button-response-btngroup { display: flex; flex-wrap: wrap; - gap: 2vmin; - max-width: 95vw; - margin: 3vmin auto; + gap: 30vw; /* Smaller gap for better centering */ + max-width: 75%; /* Wider container */ + margin: auto; /* Auto margins for centering */ justify-content: center; align-items: center; } @@ -239,6 +242,7 @@ p { min-height: 50vh; padding: 3vmin; gap: 4vmin; + max-width: 92vw; } .trial-prompt { @@ -258,7 +262,6 @@ p { margin: 3vmin 0; padding: 3vmin; width: 100%; - max-width: 95vw; gap: max(0.5vmin, calc(90vw / var(--stimuli-count, 5) * 0.1)); /* Dynamic sizing based on number of stimuli and screen space */ @@ -274,6 +277,8 @@ p { /* Special handling for many stimuli (> 7) to ensure readability */ --many-stimuli-adjustment: calc(max(0, var(--stimuli-count, 5) - 7) * 0.5vmin); --adjusted-svg-size: max(calc(var(--svg-size) + var(--many-stimuli-adjustment)), var(--min-size)); + max-width: 95vw; + } .flanker-stim span { @@ -359,10 +364,10 @@ p { /* Styles for tablets */ @media (max-width: 768px) { .jspsych-instructions-nav { - position: fixed; - bottom: 30vmin; - left: 50%; - transform: translateX(-50%); + position: fixed !important; + bottom: 30vmin !important; + left: 50% !important; + transform: translateX(-50%) !important; } /* Add padding to prevent content from going under fixed buttons */ @@ -440,14 +445,17 @@ p { bottom: 3vh; gap: 30vw; } + .target-container { - margin-bottom: 1vmin; + margin-bottom: 1vmin; } + .target-stimulus { width: 30vmin; height: 30vmin; border: 4px solid #000000bd; } + .choice-option { margin: 0; flex-basis: clamp(15vmin, calc(85vw / var(--num-choices, 4)), 25vw) !important; @@ -457,15 +465,11 @@ p { /* Flanker-specific landscape adjustments */ .flanker-stim { - gap: max(0.4vmin, calc(88vw / var(--stimuli-count, 5) * 0.09)); + gap: max(0.4vw, calc(88vw / var(--stimuli-count, 5) * 0.09)); - /* Medium screen sizing */ - --base-size: min( - calc(82vw / var(--stimuli-count, 5)), - calc(60vh / 1), - 18vmin - ); - --min-size: 3.5vmin; - --max-size: 18vmin; + /* Medium screen sizing - use vw for landscape */ + --base-size: calc(80vw / var(--stimuli-count, 5)); + --min-size: 3.5vw; + --max-size: 18.5vw; } } \ No newline at end of file diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts index dada6b02..7f7db0c2 100644 --- a/packages/flanker-inhibitory/src/text.ts +++ b/packages/flanker-inhibitory/src/text.ts @@ -41,8 +41,8 @@ export const trial_text = { fixation_cross: "+", // Feedback messages (optional) - correct_feedback: "Correct!", - incorrect_feedback: "Try again", + correct_feedback: "Correct", + incorrect_feedback: "Incorrect", // Timing messages too_slow_message: "Please respond faster", @@ -108,8 +108,9 @@ export const tts_config = { */ export const instruction_pages = [ -"حبيبي حبيبي حبيبي", "حبيبي حبيبي حبيبي", "Look at the middle fish only - ignore the other fish. See which direction the middle fish is swimming. Click the ← button if it's swimming left, click the → button if it's swimming right.", - "Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions TWOO. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions THREE. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FOUR. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FIVE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SIX Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SEVEN Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. EIGHT Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. NINE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. TEN. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. ELEVENTry to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions TWOO. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions THREE. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FOUR. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. FIVE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SIX Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. SEVEN Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. EIGHT Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. NINE Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. TEN. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. ELEVENTry to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions one. Try to respond as quickly and accurately as possible. Remember: Only pay attention to the middle fish, even if the other fish are swimming in different directions. directionsdirections directions TWOO. Try ", - "Click continue to start the practice round." + "Look at the middle fish only - ignore the other fish.", + "Click the ← button if it's swimming left, click the → button if it's swimming right.", + "Try to respond as quickly and accurately as possible.", + "Continue to start the practice round." ]; From 4500cd90cbc2ae0d2165f344a8ec5b8477d641e4 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Mon, 14 Jul 2025 16:44:37 -0400 Subject: [PATCH 32/37] chore: simplify text.ts trial_text object --- packages/flanker-inhibitory/src/index.ts | 13 +++------- packages/flanker-inhibitory/src/text.ts | 32 ++++++++---------------- 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index 3078d3e9..da6dbf24 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -529,8 +529,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { type: jsPsychHtmlButtonResponse, stimulus: `
        -

        ${trial_text.practice_header}

        -

        ${trial_text.practice_intro_message}

        +

        ${trial_text.practice_intro}

        `, choices: [trial_text.start_button], @@ -641,14 +640,10 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { timeline.push({ type: jsPsychHtmlButtonResponse, stimulus: () => { - const practice_data = jsPsych.data.get().filter({task: 'flanker', phase: 'practice'}).values(); - const performance = calculatePerformance(practice_data); return `
        -

        ${trial_text.practice_complete_header}

        -

        ${trial_text.practice_complete_message}

        -

        ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

        +

        ${trial_text.practice_outro}

        `; }, @@ -672,7 +667,6 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { type: jsPsychHtmlButtonResponse, stimulus: `
        -

        ${trial_text.main_task_header}

        ${trial_text.main_task_intro}

        `, @@ -766,8 +760,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { return `
        -

        ${trial_text.task_complete_header}

        -

        ${trial_text.task_complete_message}

        +

        ${trial_text.task_complete}

        ${trial_text.performance_title}

        ${trial_text.accuracy_label} ${performance.accuracy.toFixed(1)}%

        diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts index 7f7db0c2..e08e7ed9 100644 --- a/packages/flanker-inhibitory/src/text.ts +++ b/packages/flanker-inhibitory/src/text.ts @@ -17,39 +17,27 @@ export const trial_text = { right_button: "→", // Task completion messages - task_complete_header: "Task Complete!", - task_complete_message: "Thank you for participating in the flanker inhibitory control task.", + task_complete: "Thank you for participating in the flanker inhibitory control task.", - // Practice phase text - practice_header: "Practice Round", - practice_intro_message: "Let's practice! Look at the middle fish and click the button that shows which way it's swimming.", - practice_complete_header: "Practice Complete!", - practice_complete_message: "Great job! Now you're ready for the real task.", - practice_failed_message: "Let's try the practice again. Remember to focus on the middle fish only.", - - // Main task instructions and headers - main_task_header: "Main Task", - main_task_intro: "Now we'll start the real task. Remember to focus on the middle fish!", - main_task_prompt: "Look at the middle fish. Click the button for the direction it's swimming.", + // Practice phase text: pre-practice intro and post-practice message. + practice_intro: "Let's practice! Look at the middle fish and click the button that shows which way it's swimming.", + practice_outro: "

        Practice Complete!

        Great job! Now you're ready for the real task.", + // Main task instructions and inter-task prompt + main_task_intro: "

        Main Task

        Now we'll start the real task. Are you ready?", + main_task_prompt: "", + // Performance summary labels performance_title: "Your Performance:", accuracy_label: "Accuracy:", response_time_label: "Average Response Time:", - + // Fixation and inter-trial fixation_cross: "+", - + // Feedback messages (optional) correct_feedback: "Correct", incorrect_feedback: "Incorrect", - - // Timing messages - too_slow_message: "Please respond faster", - - // Progress messages - block_progress: "Block {current} of {total}", - trial_progress: "Trial {current} of {total}", }; // TTS (Text-to-Speech) configuration From b66b3e5b01d1d9527d573de1011e1737ae65e1aa Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 15 Jul 2025 11:56:35 -0400 Subject: [PATCH 33/37] chore: update doc parameters --- packages/flanker-inhibitory/README.md | 15 ++++-------- .../docs/flanker-inhibitory.md | 23 ++++++------------- packages/flanker-inhibitory/src/index.spec.ts | 4 ++-- packages/flanker-inhibitory/src/index.ts | 2 +- 4 files changed, 15 insertions(+), 29 deletions(-) diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md index e28b96f6..d36d152f 100644 --- a/packages/flanker-inhibitory/README.md +++ b/packages/flanker-inhibitory/README.md @@ -47,15 +47,11 @@ The following parameters can be specified in the **options** parameter. | num_practice | number | 8 | Number of practice trials | | num_trials | number | 20 | Number of main task trials | | enable_tts | boolean | false | Enable text-to-speech functionality | -| tts_voice | string | '' | Voice name for TTS (empty uses default) | -| tts_rate | number | 1.0 | Speech rate (0.1 to 10) | +| tts_method | string | 'google' | Preferred TTS method: 'google' or 'system' | +| tts_rate | number | 0.8 | Speech rate (0.1 to 10) | | tts_pitch | number | 1.0 | Speech pitch (0 to 2) | -| tts_volume | number | 1.0 | Speech volume (0 to 1) | +| tts_volume | number | 0.8 | Speech volume (0 to 1) | | tts_lang | string | 'en-US' | Language code for TTS | -| speak_instructions | boolean | true | Speak instruction page content | -| speak_prompts | boolean | true | Speak trial prompts | -| speak_feedback | boolean | true | Speak feedback messages | -| auto_speak | boolean | true | Auto-start speaking when content loads | #### Example Usage @@ -86,10 +82,9 @@ const svgTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { // With TTS enabled const ttsTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { enable_tts: true, - speak_instructions: true, - speak_prompts: true, - speak_feedback: true, + tts_method: 'google', tts_rate: 0.9, + tts_volume: 0.8, tts_lang: 'en-US' }); diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md index 0d9a97e0..5242cac7 100644 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -27,15 +27,11 @@ show_practice | boolean | true | Whether to include practice trials with feedbac num_practice | number | 8 | Number of practice trials to present num_trials | number | 20 | Number of main task trials to present enable_tts | boolean | false | Enable text-to-speech functionality -tts_voice | string | '' | Voice name for TTS (empty string uses default system voice) -tts_rate | number | 1.0 | Speech rate (0.1 to 10, where 1.0 is normal speed) +tts_method | string | 'google' | Preferred TTS method: 'google' or 'system' +tts_rate | number | 0.8 | Speech rate (0.1 to 10, where 1.0 is normal speed) tts_pitch | number | 1.0 | Speech pitch (0 to 2, where 1.0 is normal pitch) -tts_volume | number | 1.0 | Speech volume (0 to 1, where 1.0 is maximum volume) +tts_volume | number | 0.8 | Speech volume (0 to 1, where 1.0 is maximum volume) tts_lang | string | 'en-US' | Language code for speech synthesis -speak_instructions | boolean | true | Whether to speak instruction page content aloud -speak_prompts | boolean | true | Whether to speak trial prompts aloud -speak_feedback | boolean | true | Whether to speak feedback messages aloud -auto_speak | boolean | true | Whether to automatically start speaking when content loads ### Stimulus Configuration Examples @@ -65,11 +61,10 @@ const customTypeConfig = { // Enable TTS with custom voice settings const ttsConfig = { enable_tts: true, - speak_instructions: true, - speak_prompts: true, - speak_feedback: true, + tts_method: 'google', tts_rate: 0.8, tts_pitch: 1.1, + tts_volume: 0.8, tts_lang: 'en-US' }; ``` @@ -214,15 +209,11 @@ The package includes comprehensive TTS functionality for accessibility: ```js const config = { enable_tts: true, - tts_voice: 'Google US English', // Specific voice name (optional) + tts_method: 'google', // Preferred TTS method: 'google' or 'system' tts_rate: 0.9, // Slightly slower than normal tts_pitch: 1.0, // Normal pitch tts_volume: 0.8, // 80% volume - tts_lang: 'en-US', // Language code - speak_instructions: true, - speak_prompts: true, - speak_feedback: true, - auto_speak: true + tts_lang: 'en-US' // Language code }; ``` diff --git a/packages/flanker-inhibitory/src/index.spec.ts b/packages/flanker-inhibitory/src/index.spec.ts index 1e158a38..0ac2266d 100644 --- a/packages/flanker-inhibitory/src/index.spec.ts +++ b/packages/flanker-inhibitory/src/index.spec.ts @@ -307,8 +307,8 @@ describe("Flanker Inhibitory Control Task", () => { it("should have properly structured instruction pages", () => { instruction_pages.forEach((page, index) => { expect(page).toBeDefined(); - expect(page.buttons).toBeDefined(); - expect(Array.isArray(page.buttons)).toBe(true); + expect(typeof page).toBe('string'); + expect(page.length).toBeGreaterThan(0); }); }); }); diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index da6dbf24..005a5d93 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -488,7 +488,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { tts_rate = 0.8, tts_pitch = 1.0, tts_volume = 0.8, - tts_lang, + tts_lang = 'en-US', } = config; // Create TTS options object From 096dd7221562ab6a6edfba01f5e5811d6cf2e500 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 15 Jul 2025 16:11:57 -0400 Subject: [PATCH 34/37] feat: stimulus duration --- packages/flanker-inhibitory/README.md | 1 + packages/flanker-inhibitory/docs/flanker-inhibitory.md | 1 + packages/flanker-inhibitory/examples/index.html | 2 ++ packages/flanker-inhibitory/src/index.ts | 4 ++++ 4 files changed, 8 insertions(+) diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md index d36d152f..eee0ce8a 100644 --- a/packages/flanker-inhibitory/README.md +++ b/packages/flanker-inhibitory/README.md @@ -42,6 +42,7 @@ The following parameters can be specified in the **options** parameter. | svg | array | undefined | Override parameter - array of SVGs to layer (should be right-facing; left version auto-generated) | | stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd ≥3) | | fixation_duration | number | 500 | Duration of fixation cross in milliseconds | +| stimulus_duration | number | undefined | Maximum time for stimulus display before auto-advancing (undefined = no timeout) | | show_instructions | boolean | true | Whether to show instruction screens | | show_practice | boolean | true | Whether to include practice trials | | num_practice | number | 8 | Number of practice trials | diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md index 5242cac7..6c456138 100644 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -22,6 +22,7 @@ stimuli_type | string | 'layered' | Type of stimuli: 'fish', 'arrow', 'layered' svg | array | undefined | Override parameter - array of right-facing SVG strings to layer (left version auto-generated) stimuli_amount | number | 5 | Number of stimuli in flanker display (must be odd number ≥3) fixation_duration | number | 500 | Duration of fixation cross display in milliseconds +stimulus_duration | number | undefined | Maximum time for stimulus display before auto-advancing (undefined = no timeout) show_instructions | boolean | true | Whether to display instruction screens before the task show_practice | boolean | true | Whether to include practice trials with feedback num_practice | number | 8 | Number of practice trials to present diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index af7d459b..b882e028 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -46,6 +46,7 @@ stimuli_amount: 5, // Number of stimuli in flanker display (must be odd ≥3) // ===== TIMING CONFIGURATION ===== + stimulus_duration: undefined, // Maximum time for stimulus display before auto-advancing (undefined = no timeout) fixation_duration: 500, // Duration of fixation cross in milliseconds // ===== TASK FLOW CONFIGURATION ===== @@ -78,6 +79,7 @@ // Left version will be auto-generated by flipping ], stimuli_amount: 5, + stimulus_duration: 3000, // Auto-advance after 3 seconds if no response num_practice: 0, num_trials: 16 }); diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index 005a5d93..ecdf1c14 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -430,6 +430,7 @@ export interface FlankerConfig { svg?: string[]; // Override parameter - array of SVGs to layer stimuli_amount?: number; // Number of stimuli in flanker display (default: 5, must be odd ≥3) fixation_duration?: number; + stimulus_duration?: number; // Maximum time for stimulus display before auto-advancing (undefined = no timeout) show_instructions?: boolean; show_practice?: boolean; num_practice?: number; @@ -479,6 +480,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { svg, stimuli_amount = 5, fixation_duration = 500, + stimulus_duration, show_instructions = true, show_practice = true, num_practice = 8, @@ -588,6 +590,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { }, choices: [trial_text.left_button, trial_text.right_button], button_html: (choice) => ``, + trial_duration: stimulus_duration, data: { task: 'flanker', phase: 'practice', @@ -725,6 +728,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { }, choices: [trial_text.left_button, trial_text.right_button], button_html: (choice) => ``, + trial_duration: stimulus_duration, data: { task: 'flanker', phase: 'main', From 6b8aa23f73d941853d54172173fdbb6d999c392d Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 15 Jul 2025 16:41:12 -0400 Subject: [PATCH 35/37] fix: TTS bleed if user skips quick through pages, use global token --- .claude/settings.local.json | 3 +- packages/flanker-inhibitory/src/index.ts | 103 ++++++++++++++++------- 2 files changed, 74 insertions(+), 32 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 813c08d8..881913ab 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -11,7 +11,8 @@ "Bash(npm run build:*)", "Bash(npm test)", "Bash(npm run:*)", - "Bash(npx jest:*)" + "Bash(npx jest:*)", + "Bash(npm test:*)" ], "deny": [] } diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index ecdf1c14..8ea8a873 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -7,23 +7,34 @@ import { layered_stimuli, fish_only, arrow_only, custom_stimulus } from './stimu // Global audio reference for stopping Google TTS let currentGoogleAudio: HTMLAudioElement | null = null; +// Global speech cancellation token to prevent speech bleeding between pages +let currentSpeechToken = 0; + /** * Intelligent TTS with user preference support * Tries user's preferred method first, then the other method as fallback */ async function speakText(text: string, options: { lang?: string, volume?: number, method?: 'google' | 'system' } = {}) { - // Stop any current speech first and wait for it to stop + // Stop any current speech first stopAllSpeech(); + // Create a unique token for this speech request + const speechToken = ++currentSpeechToken; + + // Check if we were cancelled before starting + if (speechToken !== currentSpeechToken) return; + const preferredMethod = options.method || 'google'; // Try preferred method first try { if (preferredMethod === 'google') { - await speakWithGoogleTTS(text, options.lang || 'en'); + if (speechToken !== currentSpeechToken) return; // Check before starting + await speakWithGoogleTTS(text, options.lang || 'en', speechToken); return; } else { - await speakWithSystemTTS(text, options); + if (speechToken !== currentSpeechToken) return; // Check before starting + await speakWithSystemTTS(text, options, speechToken); return; } } catch (preferredSpeechError) { @@ -31,22 +42,20 @@ async function speakText(text: string, options: { lang?: string, volume?: number } // Try Google TTS regardless - stopAllSpeech(); - await new Promise(resolve => setTimeout(resolve, 100)); + if (speechToken !== currentSpeechToken) return; try { - await speakWithGoogleTTS(text, options.lang || 'en'); + await speakWithGoogleTTS(text, options.lang || 'en', speechToken); return; } catch (googleError) { // Google failed, continue to system } // Try system TTS as final fallback - stopAllSpeech(); - await new Promise(resolve => setTimeout(resolve, 100)); + if (speechToken !== currentSpeechToken) return; try { - await speakWithSystemTTS(text, options); + await speakWithSystemTTS(text, options, speechToken); return; } catch (systemError) { console.warn('🔊 TTS unavailable'); @@ -57,34 +66,40 @@ async function speakText(text: string, options: { lang?: string, volume?: number * Stop all speech including Google TTS audio - aggressively stops everything */ function stopAllSpeech() { - // Stop system TTS aggressively - if ('speechSynthesis' in window) { - speechSynthesis.cancel(); - speechSynthesis.pause(); - speechSynthesis.resume(); - speechSynthesis.cancel(); - } + // Increment token to cancel any pending speech + currentSpeechToken++; + try { + // Stop system TTS aggressively + speechSynthesis.cancel(); + speechSynthesis.pause(); + speechSynthesis.resume(); + speechSynthesis.cancel(); // Stop Google TTS audio aggressively - if (currentGoogleAudio) { - try { + if (currentGoogleAudio) { currentGoogleAudio.pause(); currentGoogleAudio.currentTime = 0; // Reset to beginning currentGoogleAudio.src = ''; // Clear source to stop loading - } catch (e) { - // Ignore errors, just ensure we clear the reference } - currentGoogleAudio = null; + } catch (e) { + // Ignore errors, just ensure we clear the reference } + currentGoogleAudio = null; } /** * Simple system TTS function * Browser will automatically select the best voice for the specified language */ -function speakWithSystemTTS(text: string, options: { rate?: number, volume?: number, pitch?: number, lang?: string } = {}) { +function speakWithSystemTTS(text: string, options: { rate?: number, volume?: number, pitch?: number, lang?: string } = {}, speechToken?: number) { return new Promise((resolve, reject) => { if ('speechSynthesis' in window) { + // Check if cancelled before starting + if (speechToken && speechToken !== currentSpeechToken) { + resolve(); + return; + } + // Create and speak the utterance const utterance = new SpeechSynthesisUtterance(text); @@ -99,7 +114,15 @@ function speakWithSystemTTS(text: string, options: { rate?: number, volume?: num } // Add event listeners - utterance.onstart = () => resolve(); + utterance.onstart = () => { + // Check if cancelled after starting + if (speechToken && speechToken !== currentSpeechToken) { + speechSynthesis.cancel(); + resolve(); + return; + } + resolve(); + }; utterance.onend = () => resolve(); utterance.onerror = (e) => { if (e.error === 'not-allowed' || e.error === 'synthesis-failed') { @@ -109,6 +132,12 @@ function speakWithSystemTTS(text: string, options: { rate?: number, volume?: num } }; + // Final check before speaking + if (speechToken && speechToken !== currentSpeechToken) { + resolve(); + return; + } + speechSynthesis.speak(utterance); } else { reject(new Error('speechSynthesis not supported')); @@ -120,9 +149,15 @@ function speakWithSystemTTS(text: string, options: { rate?: number, volume?: num * Defaultl TTS using Google Translate * This works by creating an audio element that plays Google's TTS service */ -function speakWithGoogleTTS(text: string, lang: string) { +function speakWithGoogleTTS(text: string, lang: string, speechToken?: number) { return new Promise((resolve, reject) => { try { + // Check if cancelled before starting + if (speechToken && speechToken !== currentSpeechToken) { + resolve(); + return; + } + // Convert language code to simple 2-letter format for Google const googleLang = lang ? lang.substring(0, 2).toLowerCase() : 'en'; // Create Google Translate TTS URL @@ -136,10 +171,10 @@ function speakWithGoogleTTS(text: string, lang: string) { currentGoogleAudio = audio; audio.oncanplay = () => { - // Check if we were cancelled while loading - if (currentGoogleAudio !== audio) { + // Check if we were cancelled while loading or if this is no longer the current audio + if ((speechToken && speechToken !== currentSpeechToken) || currentGoogleAudio !== audio) { audio.pause(); - reject(new Error('Cancelled while loading')); + resolve(); return; } audio.play().then(resolve).catch(reject); @@ -162,6 +197,12 @@ function speakWithGoogleTTS(text: string, lang: string) { reject(new Error('Google TTS failed')); }; + // Final check before loading + if (speechToken && speechToken !== currentSpeechToken) { + resolve(); + return; + } + // Load the audio audio.load(); @@ -217,16 +258,16 @@ function createInstructions(instruction_pages_data = instruction_pages, enable_t const target = event.target as HTMLElement; if (target && (target.id === 'jspsych-instructions-next' || target.id === 'jspsych-instructions-back')) { stopAllSpeech(); - // Wait longer to ensure speech has stopped before starting new speech - setTimeout(speakCurrentPage, 100); + // Remove delay - speak immediately after stopping + speakCurrentPage(); } }; // Add single event listener to document document.addEventListener('click', handleButtonClick); - // Speak initial page - setTimeout(speakCurrentPage, 100); + // Speak initial page immediately + speakCurrentPage(); } }, on_finish: function(data: any) { From 081faffd3adc3644c5b65bd34b712e875a76e949 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Thu, 24 Jul 2025 01:01:44 -0400 Subject: [PATCH 36/37] fix: dependencies --- .claude/settings.local.json | 19 - package-lock.json | 1242 ++-------------------- packages/flanker-inhibitory/package.json | 4 + 3 files changed, 94 insertions(+), 1171 deletions(-) delete mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 881913ab..00000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "permissions": { - "allow": [ - "WebFetch(domain:nihtoolbox.org)", - "WebFetch(domain:pmc.ncbi.nlm.nih.gov)", - "WebFetch(domain:www.svgrepo.com)", - "Bash(rg:*)", - "Bash(find:*)", - "Bash(ls:*)", - "WebFetch(domain:resources.nihtoolbox.org)", - "Bash(npm run build:*)", - "Bash(npm test)", - "Bash(npm run:*)", - "Bash(npx jest:*)", - "Bash(npm test:*)" - ], - "deny": [] - } -} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 04ae6c23..637813d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2484,44 +2484,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jspsych-contrib/plugin-spatial-nback": { - "name": "@jspsych/jspsych-contrib", - "resolved": "git+ssh://git@github.com/farhat60/jspsych-contrib.git#814d4f3cfb134bbcc4357b7c25b4221ffcb1674c", - "workspaces": [ - "packages/*", - "templates/cli" - ], - "dependencies": { - "gulp": "^5.0.0", - "gulp-replace": "^1.1.4" - } - }, - "node_modules/@jspsych-timelines/arrow-flanker": { - "resolved": "packages/arrow-flanker", - "link": true - }, "node_modules/@jspsych-timelines/cli": { "resolved": "templates/cli", "link": true }, - "node_modules/@jspsych-timelines/false-memory": { - "resolved": "packages/false-memory", - "link": true - }, - "node_modules/@jspsych-timelines/pattern-comparison-task": { - "resolved": "packages/pattern-comparison-task", - "link": true - }, - "node_modules/@jspsych-timelines/pattern-comparison-test": { - "resolved": "packages/pattern-comparison-test", - "link": true - }, - "node_modules/@jspsych-timelines/spatial-cueing": { - "resolved": "packages/spatial-cueing", - "link": true - }, - "node_modules/@jspsych-timelines/spatial-nback": { - "resolved": "packages/spatial-nback", + "node_modules/@jspsych-timelines/flanker-inhibitory": { + "resolved": "packages/flanker-inhibitory", "link": true }, "node_modules/@jspsych/config": { @@ -2602,15 +2570,6 @@ "jspsych": ">=7.1.0" } }, - "node_modules/@jspsych/plugin-html-keyboard-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@jspsych/plugin-html-keyboard-response/-/plugin-html-keyboard-response-2.1.0.tgz", - "integrity": "sha512-NmtYU9798mEJ6CLQREp6bNHo1e+Ca5zwAznBlREmpUMdOrqBhTLrF5T/Gg4t2MJHm4lFN/fwHJtt0g5vMAoYFg==", - "license": "MIT", - "peerDependencies": { - "jspsych": ">=7.1.0" - } - }, "node_modules/@jspsych/plugin-instructions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@jspsych/plugin-instructions/-/plugin-instructions-2.1.0.tgz", @@ -2620,14 +2579,6 @@ "jspsych": ">=7.1.0" } }, - "node_modules/@jspsych/plugin-survey-text": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@jspsych/plugin-survey-text/-/plugin-survey-text-2.0.0.tgz", - "integrity": "sha512-e30br1zD5umZrUdbhZId/00OIRejkgyExP7CWqLBOvC9i8SfBMrjuxgNeJwFf0gpdglQ+HYwE+jkXBNXdSYszQ==", - "peerDependencies": { - "jspsych": ">=7.1.0" - } - }, "node_modules/@jspsych/test-utils": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@jspsych/test-utils/-/test-utils-1.2.0.tgz", @@ -11650,6 +11601,7 @@ "packages/arrow-flanker": { "name": "@jspsych-timelines/arrow-flanker", "version": "0.2.0", + "extraneous": true, "license": "MIT", "dependencies": { "@jspsych/plugin-html-keyboard-response": "^2.0.0" @@ -11665,6 +11617,7 @@ "packages/false-memory": { "name": "@jspsych-timelines/false-memory", "version": "0.2.0", + "extraneous": true, "license": "MIT", "dependencies": { "@jspsych/plugin-html-keyboard-response": "^2.0.0", @@ -11678,10 +11631,14 @@ "jspsych": "^8.0.1" } }, - "packages/pattern-comparison-task": { - "name": "@jspsych-timelines/pattern-comparison-task", + "packages/flanker-inhibitory": { + "name": "@jspsych-timelines/flanker-inhibitory", "version": "0.0.1", "license": "MIT", + "dependencies": { + "@jspsych/plugin-html-button-response": "^2.0.0", + "@jspsych/plugin-instructions": "^2.0.0" + }, "devDependencies": { "@jspsych/config": "^3.2.2", "jspsych": "^8.0.0", @@ -11689,7 +11646,7 @@ "typescript": "^5.0.2" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/android-arm": { + "packages/flanker-inhibitory/node_modules/@esbuild/android-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", @@ -11706,7 +11663,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/android-arm64": { + "packages/flanker-inhibitory/node_modules/@esbuild/android-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", @@ -11723,7 +11680,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/android-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/android-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", @@ -11740,7 +11697,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/darwin-arm64": { + "packages/flanker-inhibitory/node_modules/@esbuild/darwin-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", @@ -11757,7 +11714,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/darwin-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/darwin-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", @@ -11774,7 +11731,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/freebsd-arm64": { + "packages/flanker-inhibitory/node_modules/@esbuild/freebsd-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", @@ -11791,7 +11748,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/freebsd-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/freebsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", @@ -11808,7 +11765,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-arm": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-arm": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", @@ -11825,7 +11782,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-arm64": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", @@ -11842,7 +11799,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-ia32": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-ia32": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", @@ -11859,7 +11816,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-loong64": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-loong64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", @@ -11876,7 +11833,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-mips64el": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-mips64el": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", @@ -11893,7 +11850,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-ppc64": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-ppc64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", @@ -11910,7 +11867,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-riscv64": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-riscv64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", @@ -11927,7 +11884,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-s390x": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-s390x": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", @@ -11944,7 +11901,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/linux-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/linux-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", @@ -11961,7 +11918,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/netbsd-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/netbsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", @@ -11978,7 +11935,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/openbsd-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/openbsd-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", @@ -11995,7 +11952,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/sunos-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/sunos-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", @@ -12012,7 +11969,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/win32-arm64": { + "packages/flanker-inhibitory/node_modules/@esbuild/win32-arm64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", @@ -12029,7 +11986,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/win32-ia32": { + "packages/flanker-inhibitory/node_modules/@esbuild/win32-ia32": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", @@ -12046,7 +12003,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/@esbuild/win32-x64": { + "packages/flanker-inhibitory/node_modules/@esbuild/win32-x64": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", @@ -12063,7 +12020,7 @@ "node": ">=12" } }, - "packages/pattern-comparison-task/node_modules/esbuild": { + "packages/flanker-inhibitory/node_modules/esbuild": { "version": "0.17.19", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", @@ -12101,7 +12058,7 @@ "@esbuild/win32-x64": "0.17.19" } }, - "packages/pattern-comparison-task/node_modules/rollup": { + "packages/flanker-inhibitory/node_modules/rollup": { "version": "3.29.5", "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", @@ -12118,7 +12075,7 @@ "fsevents": "~2.3.2" } }, - "packages/pattern-comparison-task/node_modules/source-map": { + "packages/flanker-inhibitory/node_modules/source-map": { "version": "0.8.0-beta.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", @@ -12131,7 +12088,7 @@ "node": ">= 8" } }, - "packages/pattern-comparison-task/node_modules/tr46": { + "packages/flanker-inhibitory/node_modules/tr46": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", @@ -12141,7 +12098,7 @@ "punycode": "^2.1.0" } }, - "packages/pattern-comparison-task/node_modules/tsup": { + "packages/flanker-inhibitory/node_modules/tsup": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", @@ -12187,14 +12144,14 @@ } } }, - "packages/pattern-comparison-task/node_modules/webidl-conversions": { + "packages/flanker-inhibitory/node_modules/webidl-conversions": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", "dev": true, "license": "BSD-2-Clause" }, - "packages/pattern-comparison-task/node_modules/whatwg-url": { + "packages/flanker-inhibitory/node_modules/whatwg-url": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", @@ -12206,9 +12163,10 @@ "webidl-conversions": "^4.0.2" } }, - "packages/pattern-comparison-test": { - "name": "@jspsych-timelines/pattern-comparison-test", + "packages/pattern-comparison-task": { + "name": "@jspsych-timelines/pattern-comparison-task", "version": "0.0.1", + "extraneous": true, "license": "MIT", "devDependencies": { "@jspsych/config": "^3.2.2", @@ -12217,1086 +12175,66 @@ "typescript": "^5.0.2" } }, - "packages/pattern-comparison-test/node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, + "packages/pattern-comparison-test": { + "name": "@jspsych-timelines/pattern-comparison-test", + "version": "0.0.1", + "extraneous": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.0.0", + "tsup": "^6.7.0", + "typescript": "^5.0.2" } }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, + "packages/spatial-cueing": { + "name": "@jspsych-timelines/spatial-cueing", + "version": "0.2.0", + "extraneous": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "devDependencies": { + "tsup": "^8.2.4", + "typescript": "^5.5.4" + }, + "peerDependencies": { + "jspsych": "^8.0.1" } }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, + "packages/spatial-nback": { + "name": "@jspsych-timelines/spatial-nback", + "version": "0.0.2", + "extraneous": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@jspsych-contrib/plugin-spatial-nback": "https://github.com/farhat60/jspsych-contrib/tree/main/", + "@jspsych/plugin-instructions": "^2.1.0", + "jsPsych": "npm:jspsych@^8.2.1" + }, + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" } }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, + "packages/spatial-nback-task": { + "version": "0.0.1", + "extraneous": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@jspsych/plugin-html-button-response": "^2.1.0", + "@jspsych/plugin-html-keyboard-response": "^2.1.0", + "jsPsych": "npm:jspsych@^8.2.1" + }, + "devDependencies": { + "@jspsych/config": "^3.2.2", + "jspsych": "^8.2.1", + "tsup": "^6.7.0", + "typescript": "^5.0.2" } }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/pattern-comparison-test/node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "packages/pattern-comparison-test/node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "packages/pattern-comparison-test/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "packages/pattern-comparison-test/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "packages/pattern-comparison-test/node_modules/tsup": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", - "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^4.0.0", - "cac": "^6.7.12", - "chokidar": "^3.5.1", - "debug": "^4.3.1", - "esbuild": "^0.17.6", - "execa": "^5.0.0", - "globby": "^11.0.3", - "joycon": "^3.0.1", - "postcss-load-config": "^3.0.1", - "resolve-from": "^5.0.0", - "rollup": "^3.2.5", - "source-map": "0.8.0-beta.0", - "sucrase": "^3.20.3", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=14.18" - }, - "peerDependencies": { - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "packages/pattern-comparison-test/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "packages/pattern-comparison-test/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "packages/spatial-cueing": { - "name": "@jspsych-timelines/spatial-cueing", - "version": "0.2.0", - "license": "MIT", - "devDependencies": { - "tsup": "^8.2.4", - "typescript": "^5.5.4" - }, - "peerDependencies": { - "jspsych": "^8.0.1" - } - }, - "packages/spatial-nback": { - "name": "@jspsych-timelines/spatial-nback", - "version": "0.0.2", - "license": "MIT", - "dependencies": { - "@jspsych-contrib/plugin-spatial-nback": "https://github.com/farhat60/jspsych-contrib/tree/main/", - "@jspsych/plugin-instructions": "^2.1.0", - "jsPsych": "npm:jspsych@^8.2.1" - }, - "devDependencies": { - "@jspsych/config": "^3.2.2", - "jspsych": "^8.2.1", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - } - }, - "packages/spatial-nback-task": { - "version": "0.0.1", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@jspsych/plugin-html-button-response": "^2.1.0", - "@jspsych/plugin-html-keyboard-response": "^2.1.0", - "jsPsych": "npm:jspsych@^8.2.1" - }, - "devDependencies": { - "@jspsych/config": "^3.2.2", - "jspsych": "^8.2.1", - "tsup": "^6.7.0", - "typescript": "^5.0.2" - } - }, - "packages/spatial-nback/git@https:/github.com/farhat60/jspsych-contrib/packages/plugin-spatial-nback": { - "extraneous": true - }, - "packages/spatial-nback/node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "packages/spatial-nback/node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "packages/spatial-nback/node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", - "dev": true, - "license": "MIT", - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=14.18.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "packages/spatial-nback/node_modules/source-map": { - "version": "0.8.0-beta.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", - "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "whatwg-url": "^7.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "packages/spatial-nback/node_modules/tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "packages/spatial-nback/node_modules/tsup": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-6.7.0.tgz", - "integrity": "sha512-L3o8hGkaHnu5TdJns+mCqFsDBo83bJ44rlK7e6VdanIvpea4ArPcU3swWGsLVbXak1PqQx/V+SSmFPujBK+zEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "bundle-require": "^4.0.0", - "cac": "^6.7.12", - "chokidar": "^3.5.1", - "debug": "^4.3.1", - "esbuild": "^0.17.6", - "execa": "^5.0.0", - "globby": "^11.0.3", - "joycon": "^3.0.1", - "postcss-load-config": "^3.0.1", - "resolve-from": "^5.0.0", - "rollup": "^3.2.5", - "source-map": "0.8.0-beta.0", - "sucrase": "^3.20.3", - "tree-kill": "^1.2.2" - }, - "bin": { - "tsup": "dist/cli-default.js", - "tsup-node": "dist/cli-node.js" - }, - "engines": { - "node": ">=14.18" - }, - "peerDependencies": { - "@swc/core": "^1", - "postcss": "^8.4.12", - "typescript": ">=4.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "postcss": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "packages/spatial-nback/node_modules/webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true, - "license": "BSD-2-Clause" - }, - "packages/spatial-nback/node_modules/whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } + "packages/spatial-nback/git@https:/github.com/farhat60/jspsych-contrib/packages/plugin-spatial-nback": { + "extraneous": true }, "templates/cli": { "name": "@jspsych-timelines/cli", diff --git a/packages/flanker-inhibitory/package.json b/packages/flanker-inhibitory/package.json index b032ee44..cb68b8c2 100644 --- a/packages/flanker-inhibitory/package.json +++ b/packages/flanker-inhibitory/package.json @@ -31,6 +31,10 @@ "url": "https://github.com/jspsych/jspsych-timelines/issues" }, "homepage": "https://github.com/jspsych/jspsych-timelines/tree/main/packages/flanker-inhibitory#readme", + "dependencies": { + "@jspsych/plugin-html-button-response": "^2.0.0", + "@jspsych/plugin-instructions": "^2.0.0" + }, "devDependencies": { "jspsych": "^8.0.0", "@jspsych/config": "^3.2.2", From f29b2189474011404c5605b535df52f277dd4099 Mon Sep 17 00:00:00 2001 From: "A. Hunter Farhat" Date: Tue, 18 Nov 2025 03:47:56 -0500 Subject: [PATCH 37/37] remove: buggy TTS --- packages/flanker-inhibitory/README.md | 17 - .../docs/flanker-inhibitory.md | 52 --- .../flanker-inhibitory/examples/index.html | 9 +- packages/flanker-inhibitory/src/index.ts | 345 +----------------- packages/flanker-inhibitory/src/text.ts | 26 -- 5 files changed, 8 insertions(+), 441 deletions(-) diff --git a/packages/flanker-inhibitory/README.md b/packages/flanker-inhibitory/README.md index eee0ce8a..24d2b048 100644 --- a/packages/flanker-inhibitory/README.md +++ b/packages/flanker-inhibitory/README.md @@ -47,12 +47,6 @@ The following parameters can be specified in the **options** parameter. | show_practice | boolean | true | Whether to include practice trials | | num_practice | number | 8 | Number of practice trials | | num_trials | number | 20 | Number of main task trials | -| enable_tts | boolean | false | Enable text-to-speech functionality | -| tts_method | string | 'google' | Preferred TTS method: 'google' or 'system' | -| tts_rate | number | 0.8 | Speech rate (0.1 to 10) | -| tts_pitch | number | 1.0 | Speech pitch (0 to 2) | -| tts_volume | number | 0.8 | Speech volume (0 to 1) | -| tts_lang | string | 'en-US' | Language code for TTS | #### Example Usage @@ -80,15 +74,6 @@ const svgTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { // Left-facing versions will be automatically generated by flipping }); -// With TTS enabled -const ttsTimeline = jsPsychTimelineFlankerInhibitory.createTimeline(jsPsych, { - enable_tts: true, - tts_method: 'google', - tts_rate: 0.9, - tts_volume: 0.8, - tts_lang: 'en-US' -}); - jsPsych.run([timeline]); ``` @@ -116,8 +101,6 @@ Utility functions for working with flanker stimuli and data: - **Multiple input types**: Support for SVG arrays, custom stimuli objects, or simple overrides - **Automatic flipping**: Single right-facing SVG automatically generates left-facing version - **Layered SVGs**: Stack multiple SVG elements for complex stimuli -- **Text-to-Speech**: Optional TTS support for accessibility and younger participants -- **Speech controls**: Space bar to skip speech, configurable voice settings ## Author / Citation diff --git a/packages/flanker-inhibitory/docs/flanker-inhibitory.md b/packages/flanker-inhibitory/docs/flanker-inhibitory.md index 6c456138..7ecc39b6 100644 --- a/packages/flanker-inhibitory/docs/flanker-inhibitory.md +++ b/packages/flanker-inhibitory/docs/flanker-inhibitory.md @@ -27,12 +27,6 @@ show_instructions | boolean | true | Whether to display instruction screens befo show_practice | boolean | true | Whether to include practice trials with feedback num_practice | number | 8 | Number of practice trials to present num_trials | number | 20 | Number of main task trials to present -enable_tts | boolean | false | Enable text-to-speech functionality -tts_method | string | 'google' | Preferred TTS method: 'google' or 'system' -tts_rate | number | 0.8 | Speech rate (0.1 to 10, where 1.0 is normal speed) -tts_pitch | number | 1.0 | Speech pitch (0 to 2, where 1.0 is normal pitch) -tts_volume | number | 0.8 | Speech volume (0 to 1, where 1.0 is maximum volume) -tts_lang | string | 'en-US' | Language code for speech synthesis ### Stimulus Configuration Examples @@ -59,15 +53,6 @@ const customTypeConfig = { stimuli_amount: 7 }; -// Enable TTS with custom voice settings -const ttsConfig = { - enable_tts: true, - tts_method: 'google', - tts_rate: 0.8, - tts_pitch: 1.1, - tts_volume: 0.8, - tts_lang: 'en-US' -}; ``` ### Timeline Structure @@ -195,40 +180,3 @@ const config = { - Optimized for various screen sizes - No keyboard input required -### Text-to-Speech (TTS) Support - -The package includes comprehensive TTS functionality for accessibility: - -#### Features -- **Configurable voice settings**: Rate, pitch, volume, and language -- **Selective content**: Choose what content to speak (instructions, prompts, feedback) -- **Speech controls**: Space bar to skip current speech -- **Auto-play options**: Automatically start speaking when content loads -- **Browser compatibility**: Uses Web Speech API (available in most modern browsers) - -#### TTS Configuration -```js -const config = { - enable_tts: true, - tts_method: 'google', // Preferred TTS method: 'google' or 'system' - tts_rate: 0.9, // Slightly slower than normal - tts_pitch: 1.0, // Normal pitch - tts_volume: 0.8, // 80% volume - tts_lang: 'en-US' // Language code -}; -``` - -#### Speech Controls -- **Space bar**: Skip current speech -- **Automatic cleanup**: Speech stops when task completes -- **Error handling**: Graceful fallback if TTS not supported - -#### Browser Support -TTS requires the Web Speech API, supported in: -- Chrome/Chromium browsers -- Safari (desktop and mobile) -- Edge -- Firefox (limited support) - -If TTS is not supported, the task continues normally without speech. - diff --git a/packages/flanker-inhibitory/examples/index.html b/packages/flanker-inhibitory/examples/index.html index b882e028..bf1d2572 100644 --- a/packages/flanker-inhibitory/examples/index.html +++ b/packages/flanker-inhibitory/examples/index.html @@ -53,14 +53,7 @@ show_instructions: true, // Whether to show instruction screens show_practice: true, // Whether to include practice trials num_practice: 3, // Number of practice trials - num_trials: 20, // Number of main task trials - - // ===== TEXT-TO-SPEECH CONFIGURATION ===== - enable_tts: true, // Enable text-to-speech functionality - tts_rate: 1.0, // Speech rate (0.1 to 10) - tts_pitch: 1.0, // Speech pitch (0 to 2) - tts_volume: 1.0, // Speech volume (0 to 1) - tts_lang: 'en-US', // Language code for TTS + num_trials: 20 // Number of main task trials }; // Create the timeline diff --git a/packages/flanker-inhibitory/src/index.ts b/packages/flanker-inhibitory/src/index.ts index 8ea8a873..cc5b5904 100644 --- a/packages/flanker-inhibitory/src/index.ts +++ b/packages/flanker-inhibitory/src/index.ts @@ -4,230 +4,7 @@ import jsPsychInstructions from '@jspsych/plugin-instructions'; import { trial_text, instruction_pages } from './text'; import { layered_stimuli, fish_only, arrow_only, custom_stimulus } from './stimuli'; -// Global audio reference for stopping Google TTS -let currentGoogleAudio: HTMLAudioElement | null = null; - -// Global speech cancellation token to prevent speech bleeding between pages -let currentSpeechToken = 0; - -/** - * Intelligent TTS with user preference support - * Tries user's preferred method first, then the other method as fallback - */ -async function speakText(text: string, options: { lang?: string, volume?: number, method?: 'google' | 'system' } = {}) { - // Stop any current speech first - stopAllSpeech(); - - // Create a unique token for this speech request - const speechToken = ++currentSpeechToken; - - // Check if we were cancelled before starting - if (speechToken !== currentSpeechToken) return; - - const preferredMethod = options.method || 'google'; - - // Try preferred method first - try { - if (preferredMethod === 'google') { - if (speechToken !== currentSpeechToken) return; // Check before starting - await speakWithGoogleTTS(text, options.lang || 'en', speechToken); - return; - } else { - if (speechToken !== currentSpeechToken) return; // Check before starting - await speakWithSystemTTS(text, options, speechToken); - return; - } - } catch (preferredSpeechError) { - // Preferred method failed, continue to try all methods - } - - // Try Google TTS regardless - if (speechToken !== currentSpeechToken) return; - - try { - await speakWithGoogleTTS(text, options.lang || 'en', speechToken); - return; - } catch (googleError) { - // Google failed, continue to system - } - - // Try system TTS as final fallback - if (speechToken !== currentSpeechToken) return; - - try { - await speakWithSystemTTS(text, options, speechToken); - return; - } catch (systemError) { - console.warn('🔊 TTS unavailable'); - } -} - -/** - * Stop all speech including Google TTS audio - aggressively stops everything - */ -function stopAllSpeech() { - // Increment token to cancel any pending speech - currentSpeechToken++; - - try { - // Stop system TTS aggressively - speechSynthesis.cancel(); - speechSynthesis.pause(); - speechSynthesis.resume(); - speechSynthesis.cancel(); - // Stop Google TTS audio aggressively - if (currentGoogleAudio) { - currentGoogleAudio.pause(); - currentGoogleAudio.currentTime = 0; // Reset to beginning - currentGoogleAudio.src = ''; // Clear source to stop loading - } - } catch (e) { - // Ignore errors, just ensure we clear the reference - } - currentGoogleAudio = null; -} - -/** - * Simple system TTS function - * Browser will automatically select the best voice for the specified language - */ -function speakWithSystemTTS(text: string, options: { rate?: number, volume?: number, pitch?: number, lang?: string } = {}, speechToken?: number) { - return new Promise((resolve, reject) => { - if ('speechSynthesis' in window) { - // Check if cancelled before starting - if (speechToken && speechToken !== currentSpeechToken) { - resolve(); - return; - } - - // Create and speak the utterance - const utterance = new SpeechSynthesisUtterance(text); - - // Apply options with defaults - utterance.rate = options.rate ?? 0.8; - utterance.volume = options.volume ?? 0.8; - utterance.pitch = options.pitch ?? 1.0; - - // Set language if provided (browser will pick best voice) - if (options.lang) { - utterance.lang = options.lang; - } - - // Add event listeners - utterance.onstart = () => { - // Check if cancelled after starting - if (speechToken && speechToken !== currentSpeechToken) { - speechSynthesis.cancel(); - resolve(); - return; - } - resolve(); - }; - utterance.onend = () => resolve(); - utterance.onerror = (e) => { - if (e.error === 'not-allowed' || e.error === 'synthesis-failed') { - reject(new Error(e.error)); // Reject on critical errors - } else { - resolve(); // Don't fail on minor errors since this is a fallback - } - }; - - // Final check before speaking - if (speechToken && speechToken !== currentSpeechToken) { - resolve(); - return; - } - - speechSynthesis.speak(utterance); - } else { - reject(new Error('speechSynthesis not supported')); - } - }); -} - -/** - * Defaultl TTS using Google Translate - * This works by creating an audio element that plays Google's TTS service - */ -function speakWithGoogleTTS(text: string, lang: string, speechToken?: number) { - return new Promise((resolve, reject) => { - try { - // Check if cancelled before starting - if (speechToken && speechToken !== currentSpeechToken) { - resolve(); - return; - } - - // Convert language code to simple 2-letter format for Google - const googleLang = lang ? lang.substring(0, 2).toLowerCase() : 'en'; - // Create Google Translate TTS URL - const encodedText = encodeURIComponent(text); - const googleTTSUrl = `https://translate.google.com/translate_tts?ie=UTF-8&tl=${googleLang}&client=tw-ob&q=${encodedText}`; - - // Create and play audio - const audio = new Audio(googleTTSUrl); - - // Store reference to current audio for stopping immediately - currentGoogleAudio = audio; - - audio.oncanplay = () => { - // Check if we were cancelled while loading or if this is no longer the current audio - if ((speechToken && speechToken !== currentSpeechToken) || currentGoogleAudio !== audio) { - audio.pause(); - resolve(); - return; - } - audio.play().then(resolve).catch(reject); - }; - - audio.onended = () => { - // Only clear if this is still the current audio - if (currentGoogleAudio === audio) { - currentGoogleAudio = null; - } - resolve(); - }; - - audio.onerror = (e) => { - // Always try to pause and clear, even on error - audio.pause(); - if (currentGoogleAudio === audio) { - currentGoogleAudio = null; - } - reject(new Error('Google TTS failed')); - }; - - // Final check before loading - if (speechToken && speechToken !== currentSpeechToken) { - resolve(); - return; - } - - // Load the audio - audio.load(); - - } catch (error) { - reject(error); - } - }); -} - -/** - * Creates instruction pages with configurable text and TTS support - * Uses the jsPsych instructions plugin with simple HTML strings - */ -// Helper function to extract text from HTML for TTS -function extractTextFromHtml(htmlString: string): string { - // Use DOMParser for robust HTML to text extraction - const parser = new DOMParser(); - const doc = parser.parseFromString(htmlString, 'text/html'); - return doc.body.textContent?.replace(/\s+/g, ' ').trim() || ''; -} - -function createInstructions(instruction_pages_data = instruction_pages, enable_tts = false, ttsOptions = {}) { - // Closure variable to store the handler for cleanup - let handleButtonClick: ((event: Event) => void) | null = null; - +function createInstructions(instruction_pages_data = instruction_pages) { return { type: jsPsychInstructions, pages: instruction_pages_data.map(page => `

        ${page}

        `), @@ -237,46 +14,7 @@ function createInstructions(instruction_pages_data = instruction_pages, enable_t key_backward: 'ArrowLeft', button_label_previous: trial_text.back_button, button_label_next: trial_text.next_button, - on_start: function() { - stopAllSpeech(); - }, - on_load: function() { - if (enable_tts) { - // Function to speak current page content - const speakCurrentPage = () => { - const instructionsContent = document.querySelector('.instructions-container'); - if (instructionsContent) { - const pageText = extractTextFromHtml(instructionsContent.innerHTML); - if (pageText.trim()) { - speakText(pageText, ttsOptions); - } - } - }; - - // Use closure variable for handler - handleButtonClick = (event: Event) => { - const target = event.target as HTMLElement; - if (target && (target.id === 'jspsych-instructions-next' || target.id === 'jspsych-instructions-back')) { - stopAllSpeech(); - // Remove delay - speak immediately after stopping - speakCurrentPage(); - } - }; - - // Add single event listener to document - document.addEventListener('click', handleButtonClick); - - // Speak initial page immediately - speakCurrentPage(); - } - }, on_finish: function(data: any) { - stopAllSpeech(); - // Clean up event listener using closure variable - if (handleButtonClick) { - document.removeEventListener('click', handleButtonClick); - handleButtonClick = null; - } data.phase = 'instructions'; } }; @@ -476,14 +214,6 @@ export interface FlankerConfig { show_practice?: boolean; num_practice?: number; num_trials?: number; - - // Simplified TTS Configuration - enable_tts?: boolean; // Enable text-to-speech functionality - tts_method?: 'google' | 'system'; // Preferred TTS method (default: 'google') - tts_rate?: number; // Speech rate (0.1 to 10, default: 0.8) - tts_pitch?: number; // Speech pitch (0 to 2, default: 1.0) - tts_volume?: number; // Speech volume (0 to 1, default: 0.8) - tts_lang?: string; // Language code for TTS } // ============================================================================ @@ -526,23 +256,8 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { show_practice = true, num_practice = 8, num_trials = 20, - enable_tts = false, - tts_method = 'google', - tts_rate = 0.8, - tts_pitch = 1.0, - tts_volume = 0.8, - tts_lang = 'en-US', } = config; - // Create TTS options object - const ttsOptions = { - rate: tts_rate, - volume: tts_volume, - pitch: tts_pitch, - lang: tts_lang, - method: tts_method - }; - // Determine which stimuli to use based on priority: // 1. svg override parameter (highest priority) // 2. stimuli_type selection @@ -562,7 +277,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { // Instructions if (show_instructions) { - timeline.push(createInstructions(instruction_pages, enable_tts, ttsOptions)); + timeline.push(createInstructions(instruction_pages)); } // Practice @@ -580,13 +295,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { data: { task: 'practice-intro', phase: 'practice' - }, - on_load: enable_tts ? function() { - const content = document.querySelector('.practice-instructions'); - if (content) { - speakText(extractTextFromHtml(content.innerHTML), ttsOptions); - } - } : undefined + } }); // Only add practice trials if num_practice > 0 @@ -638,12 +347,6 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { direction: jsPsych.timelineVariable('direction'), congruent: jsPsych.timelineVariable('congruent') }, - on_load: enable_tts ? function() { - const prompt = document.querySelector('.trial-prompt'); - if (prompt) { - speakText(extractTextFromHtml(prompt.innerHTML), ttsOptions); - } - } : undefined, on_finish: function(data: any) { const correct_response = data.direction === 'left' ? 0 : 1; data.correct = data.response === correct_response; @@ -663,13 +366,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { data: { task: 'feedback', phase: 'practice' - }, - on_load: enable_tts ? function() { - const feedback = document.querySelector('.feedback'); - if (feedback) { - speakText(extractTextFromHtml(feedback.innerHTML), ttsOptions); - } - } : undefined + } }; // Practice timeline @@ -696,13 +393,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { data: { task: 'practice-complete', phase: 'practice' - }, - on_load: enable_tts ? function() { - const content = document.querySelector('.practice-instructions'); - if (content) { - speakText(extractTextFromHtml(content.innerHTML), ttsOptions); - } - } : undefined + } }); } @@ -719,13 +410,7 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { data: { task: 'main-intro', phase: 'main' - }, - on_load: enable_tts ? function() { - const content = document.querySelector('.ready-screen'); - if (content) { - speakText(extractTextFromHtml(content.innerHTML), ttsOptions); - } - } : undefined + } }); // Main trials - only add if num_trials > 0 @@ -776,12 +461,6 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { direction: jsPsych.timelineVariable('direction'), congruent: jsPsych.timelineVariable('congruent') }, - on_load: enable_tts ? function() { - const prompt = document.querySelector('.trial-prompt'); - if (prompt) { - speakText(extractTextFromHtml(prompt.innerHTML), ttsOptions); - } - } : undefined, on_finish: function(data: any) { const correct_response = data.direction === 'left' ? 0 : 1; data.correct = data.response === correct_response; @@ -819,15 +498,6 @@ export function createTimeline(jsPsych: JsPsych, config: FlankerConfig = {}) { data: { task: 'complete', phase: 'completion' - }, - on_load: enable_tts ? function() { - const content = document.querySelector('.ready-screen'); - if (content) { - speakText(extractTextFromHtml(content.innerHTML), ttsOptions); - } - } : undefined, - on_finish: function() { - stopAllSpeech(); } }); @@ -851,8 +521,7 @@ export const utils = { calculatePerformance, createFlankerStim, createPracticeStim, - createInstructions, - speakText + createInstructions }; // Export text and createInstructions for external use diff --git a/packages/flanker-inhibitory/src/text.ts b/packages/flanker-inhibitory/src/text.ts index e08e7ed9..dc18957d 100644 --- a/packages/flanker-inhibitory/src/text.ts +++ b/packages/flanker-inhibitory/src/text.ts @@ -40,32 +40,6 @@ export const trial_text = { incorrect_feedback: "Incorrect", }; -// TTS (Text-to-Speech) configuration -export const tts_config = { - // Voice settings - voice_name: '', // Empty string uses default voice, or specify like 'Google US English' - rate: 1.0, // Speech rate (0.1 to 10) - pitch: 1.0, // Speech pitch (0 to 2) - volume: 1.0, // Speech volume (0 to 1) - - // Language and locale - lang: 'en-US', // Language code for speech synthesis - - // What text to speak aloud (can be disabled per element) - speak_instructions: true, // Speak instruction page content - speak_prompts: true, // Speak trial prompts - speak_feedback: true, // Speak correct/incorrect feedback - speak_completion: true, // Speak task completion messages - - // Auto-play settings - auto_speak_on_load: true, // Automatically start speaking when page loads - speak_button_text: false, // Whether to also speak button labels - - // Timing - speech_delay: 500, // Delay before starting speech (ms) - allow_skip: true, // Allow users to skip/stop speech -}; - /* * This is an array of page objects that have configurable texts for the instruction pages * before the actual trials. Researchers can modify these instructions to change the