diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 1ddaaffe..b77b4ddd 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -52,7 +52,6 @@ module.exports = { }, parser: '@babel/eslint-parser', parserOptions: { - parser: 'babel-eslint', ecmaVersion: 2018, sourceType: 'module', requireConfigFile: false, diff --git a/.github/workflows/comment-lint-results.yml b/.github/workflows/comment-lint-results.yml new file mode 100644 index 00000000..5bba5d1e --- /dev/null +++ b/.github/workflows/comment-lint-results.yml @@ -0,0 +1,151 @@ +name: Comment ESLint Results + +on: + workflow_run: + workflows: ["Run ESLint"] + types: + - completed + +jobs: + comment-lint-results: + # Only run this job if the previous workflow failed (indicating lint errors) + if: github.event.workflow_run.conclusion == 'failure' + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Download Lint Results + id: download + uses: actions/github-script@v6 + with: + script: | + try { + core.info('Downloading lint results artifact'); + + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.event.workflow_run.id }} + }); + + const matchArtifact = artifacts.data.artifacts.find(artifact => artifact.name === "lint-results"); + if (!matchArtifact) { + core.setFailed('No lint results artifact found'); + return false; + } + + core.info(`Found artifact id: ${matchArtifact.id}`); + + const download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip' + }); + + const fs = require('fs'); + try { + fs.writeFileSync('lint-results.zip', Buffer.from(download.data)); + core.info('Successfully downloaded and saved artifact'); + return true; + } catch (error) { + core.setFailed(`Failed to write artifact: ${error.message}`); + return false; + } + } catch (error) { + core.setFailed(`Failed to download artifact: ${error.message}`); + return false; + } + result-encoding: string + + - name: Extract Lint Results + if: steps.download.outputs.result == 'true' + id: extract + run: | + mkdir -p lint-results + unzip -o lint-results.zip -d lint-results + + if [ -f lint-results/errors.md ]; then + # Use GitHub's EOF syntax for multiline content + echo "LINT_ERRORS<> $GITHUB_ENV + cat lint-results/errors.md >> $GITHUB_ENV + echo "EOF" >> $GITHUB_ENV + echo "HAS_ERRORS=true" >> $GITHUB_OUTPUT + else + echo "HAS_ERRORS=false" >> $GITHUB_OUTPUT + fi + + - name: Find PR Number + if: steps.extract.outputs.HAS_ERRORS == 'true' + id: find-pr + uses: actions/github-script@v6 + with: + script: | + try { + core.info('Finding associated PR'); + + const { owner, repo } = context.repo; + const run_id = ${{ github.event.workflow_run.id }}; + + // Get the triggering workflow run + const run = await github.rest.actions.getWorkflowRun({ + owner, + repo, + run_id + }); + + // Find associated PR - first try the event payload + if (run.data.pull_requests && run.data.pull_requests.length > 0) { + core.info(`Found PR directly from run data: #${run.data.pull_requests[0].number}`); + return run.data.pull_requests[0].number; + } + + // Fallback to searching by head SHA + core.info(`Searching for PR using head SHA: ${run.data.head_sha}`); + const pulls = await github.rest.pulls.list({ + owner, + repo, + state: 'open', + head: `${owner}:${run.data.head_branch}` + }); + + if (pulls.data.length > 0) { + core.info(`Found PR by branch: #${pulls.data[0].number}`); + return pulls.data[0].number; + } + + core.info('No PR found for this workflow run'); + return null; + } catch (error) { + core.setFailed(`Failed to find PR: ${error.message}`); + return null; + } + result-encoding: string + + - name: Post Lint Results as PR Comment + if: steps.find-pr.outputs.result != 'null' && steps.find-pr.outputs.result != '' + uses: actions/github-script@v6 + with: + script: | + try { + const pr_number = parseInt(${{ steps.find-pr.outputs.result }}); + + if (isNaN(pr_number)) { + core.warning('Invalid PR number'); + return; + } + + core.info(`Posting comment to PR #${pr_number}`); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr_number, + body: process.env.LINT_ERRORS + }); + + core.info(`Successfully posted lint errors comment to PR #${pr_number}`); + } catch (error) { + core.setFailed(`Failed to post comment: ${error.message}`); + } \ No newline at end of file diff --git a/.github/workflows/comment-test-results.yml b/.github/workflows/comment-test-results.yml new file mode 100644 index 00000000..90f2acf1 --- /dev/null +++ b/.github/workflows/comment-test-results.yml @@ -0,0 +1,114 @@ +name: Comment Test Results + +on: + workflow_run: + workflows: ["Run Blits Tests"] + types: + - completed + +jobs: + comment-test-results: + runs-on: ubuntu-latest + permissions: + pull-requests: write + + steps: + - name: Download Test Results + uses: actions/github-script@v6 + with: + script: | + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: ${{ github.event.workflow_run.id }} + }); + + const matchArtifact = artifacts.data.artifacts.find(artifact => artifact.name === "test-results"); + if (!matchArtifact) { + core.setFailed('No test results artifact found'); + return; + } + + const download = await github.rest.actions.downloadArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: matchArtifact.id, + archive_format: 'zip' + }); + + const fs = require('fs'); + fs.writeFileSync('test-results.zip', Buffer.from(download.data)); + + - name: Extract Test Results + run: | + mkdir -p test-results + unzip -o test-results.zip -d test-results + + TIMESTAMP=$(cat test-results/timestamp.txt) + SUMMARY=$(cat test-results/summary.txt) + FAILED=$(cat test-results/failed.txt) + + echo "TIMESTAMP=$TIMESTAMP" >> $GITHUB_ENV + echo "SUMMARY=$SUMMARY" >> $GITHUB_ENV + echo "FAILED=$FAILED" >> $GITHUB_ENV + + # Check if error.txt exists and read it if it does + if [ -f test-results/error.txt ]; then + # Store raw error output in a file for the next step + cat test-results/error.txt > raw_error.txt + fi + + - name: Find PR Number + id: find-pr + uses: actions/github-script@v6 + with: + script: | + const { owner, repo } = context.repo; + const run_id = ${{ github.event.workflow_run.id }}; + + // Get the triggering workflow run + const run = await github.rest.actions.getWorkflowRun({ + owner, + repo, + run_id + }); + + // Find associated PR + const pulls = await github.rest.pulls.list({ + owner, + repo, + state: 'open', + head: run.data.head_sha + }); + + if (pulls.data.length > 0) { + return pulls.data[0].number; + } + + console.log('No PR found'); + return null; + result-encoding: string + + - name: Post Test Results as PR Comment + if: steps.find-pr.outputs.result != 'null' + uses: actions/github-script@v6 + with: + script: | + const fs = require('fs'); + const pr_number = parseInt(${{ steps.find-pr.outputs.result }}); + const status = process.env.FAILED === 'true' ? '❌ FAILED' : '✅ PASSED'; + + let commentBody = `#### Test Results: ${status}\n**Run at:** ${process.env.TIMESTAMP}\n\n**Summary:**\n${process.env.SUMMARY}`; + + // Add error output if it exists + if (fs.existsSync('raw_error.txt')) { + const errorOutput = fs.readFileSync('raw_error.txt', 'utf8'); + commentBody += '\n\n**Error Output:**\n```\n' + errorOutput + '\n```'; + } + + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr_number, + body: commentBody + }); \ No newline at end of file diff --git a/.github/workflows/run-blits-tests.yml b/.github/workflows/run-blits-tests.yml index d4fddef8..6db625a0 100644 --- a/.github/workflows/run-blits-tests.yml +++ b/.github/workflows/run-blits-tests.yml @@ -1,7 +1,7 @@ name: Run Blits Tests on: - pull_request_target: + pull_request: branches: - dev - master @@ -9,13 +9,12 @@ on: jobs: run-blits-tests: runs-on: ubuntu-latest + outputs: + tests_failed: ${{ steps.analyze-results.outputs.result && fromJSON(steps.analyze-results.outputs.result).testsFailed }} steps: - name: Checkout Code uses: actions/checkout@v3 - with: - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} - name: Set up Node.js uses: actions/setup-node@v3 @@ -26,44 +25,92 @@ jobs: run: npm install - name: Run Tests - run: npm run test > test-report.txt - #To ensure subsequent steps run even if tests fail - continue-on-error: true + id: run-tests + run: npm run test > test-report.txt 2>&1 || echo "Command failed with exit code $?" >> test-report.txt - - name: Get Current Timestamp - id: timestamp - run: echo "timestamp=$(date)" >> $GITHUB_OUTPUT + - name: Analyze Test Results + id: analyze-results + uses: actions/github-script@v6 + with: + result-encoding: json + script: | + const fs = require('fs'); + const timestamp = new Date().toISOString(); - - name: Extract Test Summary - id: extracted-test-summary - run: | - # Search for a line containing both "passed:" and "failed:" - summary=$(grep -E "passed:.*failed:" test-report.txt | head -n1) + const results = { + timestamp, + summary: '', + testsFailed: false, + error: null + }; - # Extract the number of failed tests - failed=$(echo "$summary" | sed -E 's/.*failed:\s*([0-9]+).*/\1/') + // Check if test report exists and has content + if (!fs.existsSync('test-report.txt') || !fs.statSync('test-report.txt').size) { + results.summary = 'Test execution error: No test output generated.'; + results.testsFailed = true; + return outputResults(results); + } - echo "summary=$summary" >> $GITHUB_OUTPUT + // Read test report + const testOutput = fs.readFileSync('test-report.txt', 'utf8'); - # If the number of failed tests is greater than 1, exit with error. - if [ "$failed" -gt 0 ]; then - exit 1 - fi + // Check for errors and test results + const hasError = testOutput.includes('Error'); + const testSummaryMatch = testOutput.match(/passed:\s*(\d+)\s*failed:\s*(\d+)\s*of\s*(\d+)\s*tests/); - - name: Post Test Results as PR Comment - if: always() - uses: actions/github-script@v6 - env: - SUMMARY: ${{ steps.extracted-test-summary.outputs.summary }} - TIMESTAMP: ${{ steps.timestamp.outputs.timestamp }} - with: - script: | - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: `**Test Results Summary:** (Ran at: ${process.env.TIMESTAMP})\n${process.env.SUMMARY}` - }); + if (hasError && (!testSummaryMatch || testSummaryMatch[3] === '0')) { + // Error with no tests or zero tests + results.summary = 'Test execution encountered errors. No tests were run.'; + results.testsFailed = true; + results.error = testOutput; + } else if (testSummaryMatch) { + // We have a test summary + const passed = parseInt(testSummaryMatch[1]); + const failed = parseInt(testSummaryMatch[2]); + const total = parseInt(testSummaryMatch[3]); + + results.summary = testSummaryMatch[0]; + + if (failed > 0 || total === 0) { + results.testsFailed = true; + + if (total === 0) { + results.summary = 'No tests were found to run. This is considered a failure.'; + } + } + } else { + // No test summary found + results.summary = 'Test execution produced no valid test summary'; + results.testsFailed = true; + results.error = testOutput; + } + + return outputResults(results); + + /** + * Outputs results and saves result files + */ + function outputResults(results) { + // Create test results directory and files + try { + fs.mkdirSync('./test-results', { recursive: true }); + + // Write results files + fs.writeFileSync('./test-results/timestamp.txt', results.timestamp); + fs.writeFileSync('./test-results/summary.txt', results.summary); + fs.writeFileSync('./test-results/failed.txt', results.testsFailed.toString()); + + // Save error output if it exists + if (results.error) { + fs.writeFileSync('./test-results/error.txt', results.error); + } + } catch (err) { + console.error('Error saving test results:', err); + core.warning(`Error saving test results: ${err.message}`); + } + + return results; + } - name: Upload Test Report Artifact if: always() @@ -71,3 +118,15 @@ jobs: with: name: test-report path: test-report.txt + + - name: Upload Test Results + if: always() + uses: actions/upload-artifact@v4 + with: + name: test-results + path: ./test-results/ + + # Fail the workflow if tests failed + - name: Check Test Status + if: ${{ steps.analyze-results.outputs.result && fromJSON(steps.analyze-results.outputs.result).testsFailed }} + run: exit 1 \ No newline at end of file diff --git a/.github/workflows/run-lint.yml b/.github/workflows/run-lint.yml new file mode 100644 index 00000000..efbcec7f --- /dev/null +++ b/.github/workflows/run-lint.yml @@ -0,0 +1,118 @@ +name: Run ESLint + +on: + pull_request: + branches: + - dev + - master + +jobs: + run-lint: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + with: + # Needed to compare changes against base branch + fetch-depth: 0 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: 20.16.0 + + - name: Install Dependencies + run: npm install + + - name: Get list of changed JS files + id: changed-files + run: | + # Get the list of changed .js files compared to the base branch + git diff --name-only --diff-filter=ACMRT origin/${{ github.base_ref }} HEAD | grep '\.js$' > js-changed-files.txt || true + + if [ -s js-changed-files.txt ]; then + echo "has_js_changes=true" >> $GITHUB_OUTPUT + cat js-changed-files.txt | tr '\n' ' ' > js-changed-files-spaces.txt + echo "files=$(cat js-changed-files-spaces.txt)" >> $GITHUB_OUTPUT + else + echo "has_js_changes=false" >> $GITHUB_OUTPUT + fi + + - name: Run ESLint on changed files + id: eslint + if: steps.changed-files.outputs.has_js_changes == 'true' + run: | + # Create directory for output + mkdir -p ./lint-results + + # Run ESLint only on changed files with the project's config + # Filtering out warnings - only keep errors + npx eslint --config .eslintrc.cjs ${{ steps.changed-files.outputs.files }} -f json > ./lint-results/eslint-output.json || true + + # Check if there are any errors in the ESLint output + if [ -s ./lint-results/eslint-output.json ]; then + error_count=$(node -e " + const results = require('./lint-results/eslint-output.json'); + let errorCount = 0; + for (const file of results) { + errorCount += file.errorCount || 0; + } + console.log(errorCount); + ") + + echo "errors_found=$error_count" >> $GITHUB_OUTPUT + + if [ "$error_count" -gt 0 ]; then + # Create the formatted error report + echo '❌ **Linting errors found**' > ./lint-results/errors.md + echo '' >> ./lint-results/errors.md + echo 'These linting errors must be fixed before this PR can be merged:' >> ./lint-results/errors.md + echo '' >> ./lint-results/errors.md + echo '```' >> ./lint-results/errors.md + + # Extract and format errors from the JSON + error_count=$(node -e " + const results = require('./lint-results/eslint-output.json'); + let errorSummary = ''; + + for (const file of results) { + if (file.errorCount > 0) { + const filePath = file.filePath.replace(process.env.GITHUB_WORKSPACE + '/', ''); + errorSummary += filePath + '\\n'; + + for (const msg of file.messages) { + if (msg.severity === 2) { + errorSummary += ' Line ' + msg.line + ':' + msg.column + ': ' + + msg.message + ' (' + msg.ruleId + ')\\n'; + } + } + errorSummary += '\\n'; + } + } + + console.log(errorSummary.trim()); + ") + + echo "$error_count" >> ./lint-results/errors.md + echo '```' >> ./lint-results/errors.md + echo '' >> ./lint-results/errors.md + echo '_Run `npm run lint:fix` to automatically fix some of these issues._' >> ./lint-results/errors.md + fi + else + echo "errors_found=0" >> $GITHUB_OUTPUT + fi + + - name: Upload Lint Results + if: steps.changed-files.outputs.has_js_changes == 'true' + uses: actions/upload-artifact@v4 + with: + name: lint-results + path: ./lint-results/ + + # Fail the workflow if linting errors found + - name: Check Lint Status + if: steps.changed-files.outputs.has_js_changes == 'true' && steps.eslint.outputs.errors_found != '0' + run: exit 1 \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 00000000..2312dc58 --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1 @@ +npx lint-staged diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 00000000..589c1136 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1 @@ +node ./scripts/prepush.js \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c9293ffe..26cd35f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,37 @@ # Changelog +_15 apr 2025_ + +## v1.27.1 + +- Fixed issue with global watcher on router state not being cleared out +- Minor performance updates +- Fixed issue with retrieving correct renderer version for logging +- Fixed issue with nested layouts not properly updating parent layout + +_11 apr 2025_ + +## v1.27.0 + +- Added pre push linting check +- Updated Github workflow for tests +- Added noop announcement for when announcer is disabled +- Add ability to interrupt specific announcement messages + +_09 apr 2025_ + +## v1.26.1 / v1.26.2 + +- Fixed issue with announcer queue + +_08 apr 2025_ + ## v1.26.0 - Announcer updates: queue, cancel individual messages, promise based chaining - Fixed naming collision for `config`-key on component instance - Fixed logging correct renderer version -_08 apr 2025_ - - ## v1.25.1 _01 apr 2025_ diff --git a/docs/_sidebar.md b/docs/_sidebar.md index 89586c92..c2498d0d 100644 --- a/docs/_sidebar.md +++ b/docs/_sidebar.md @@ -28,6 +28,11 @@ - [Basics](/router/basics.md) - [Hooks](/router/hooks.md) - [Transitions](/router/transitions.md) +- Shaders + - [Importing Shaders](/shaders/importing-shader.md) + - [WebGl Shaders](/shaders/webgl-shadertypes.md) + - [Canvas Shaders](/shaders/canvas-shadertypes.md) + - [v2 Conversion Guide](/shaders/v2-conversion-guide.md) - Plugins - [Text-to-Speech / Announcer](/plugins/text-to-speech-announcer.md) - [Language](/plugins/language.md) diff --git a/docs/essentials/element_attributes.md b/docs/essentials/element_attributes.md index d5108f9c..94a349f3 100644 --- a/docs/essentials/element_attributes.md +++ b/docs/essentials/element_attributes.md @@ -193,3 +193,50 @@ By default contents inside an Element (i.e. child Elements) will overflow the bo In order to contain / cut off the content inside an Element's `w` and `h`, you can add the `clipping="true"`-attribute. Setting `clipping` to `false` restores the default behaviour of content overflowing. Alternatively you can also use the `overflow`-attribute (and pass it `true` or `false`), which works similar to clipping just mapped inversly (i.e. `overflow="false"` ensures content that surpasses the parent dimensions is clipped-off). + +## Shaders + +Generally Elements that have a color or texture are simply rendered as a rectangle. With shaders you can add extra effects or even change the shape of what is rendered. + +In Blits there are two ways to apply these Shaders. + +### Built-in Element Shader attributes +For better a better development experience Blits had the following shader attributes regularly used in app development: + +- `rounded` - Allows you to round corners of an Element. You can do this with a single value, array, or object. ([details](https://lightningjs.io/api/renderer/interfaces/Renderer.RoundedProps.html)) +- `border` - Allows you to add an inner border to an Element. You can do this with an object. ([details](https://lightningjs.io/api/renderer/interfaces/Renderer.BorderProps.html)) +- `shadow` - Allows you to add a box shadow "behind" an Element. You can do this with an object. ([details](https://lightningjs.io/api/renderer/interfaces/Renderer.ShadowProps.html)) + + +```xml + + + +``` + +You can also use built-in shader attributes in combination with eachother f.e; + +```xml + +``` + +### Using shader attribute +> [!WARNING] +> This attribute does not work in combination with the built-in element shader attributes. This has to do with the complexity of shaders that makes mixing and matching quite heavy on performance. + +You can use a custom shader type by using the `shader` attribute. You can use [imported shaders](../shaders/importing-shaders.md) or some of the shaders Blits already has available to you: + +- `linearGradient` - linear gradient with multiple stops, and adjustable angle. +- `radialGradient` - radial gradient with multiple stops, and adjustable center point. +- `holePunch` - hole punch effect into a texture. + +```xml + + + + +``` + + + diff --git a/docs/plugins/text-to-speech-announcer.md b/docs/plugins/text-to-speech-announcer.md index 9e237e7f..1ad2da19 100644 --- a/docs/plugins/text-to-speech-announcer.md +++ b/docs/plugins/text-to-speech-announcer.md @@ -81,6 +81,8 @@ Imagine an App with a row of tiles, it's possible that before the title of the r The `speak()`-method return a Promise that also contains a `cancel()` function. When called, it will cancel that specific message and remove it from the queue before it can be spoken out. +Additionally if you want to _interrupt_ a specific messages as it's being spoken out as well and go straight to the next message in the queue (i.e. the newly focused item, for example). You can use the `stop()` message that is returned on the Promise returned by the `speak()`-method. + ```js Blits.Component('MyTile', { // @@ -96,7 +98,9 @@ Blits.Component('MyTile', { this.message = this.$announcer.speak(`This is tile ${this.title}`) }, unfocus() { - // when unfocused cancel the message and remove it from the queue + // when unfocused interrupt the message if it's already being spoken out + this.message.stop() + // and cancel the message to remove it from the queue this.message.cancel() } } diff --git a/docs/shaders/canvas-shadertypes.md b/docs/shaders/canvas-shadertypes.md new file mode 100644 index 00000000..10c53a3e --- /dev/null +++ b/docs/shaders/canvas-shadertypes.md @@ -0,0 +1,81 @@ +# Canvas ShaderTypes +The [Canvas API](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API) does not really have a concept called shaders, however for it to fit better into our architecture we are calling it shaders anyway. You can use the following properties to define your `CanvasShaderType`. + +## render +The render property is the only `required` property for the CanvasShaderType. This property requires a function that draws the shader/effect. + +```js +export const Border = { + render(ctx, quad, renderContext) { + //renders node context(if not called the context won't be drawn) + renderContext() + + ctx.strokeStyle = 'green' + ctx.lineWidth = 10 + ctx.beginPath() + //draw an innerBorder + ctx.strokeRect( + quad.tx + 5, + quad.ty + 5, + quad.width - 10, + quad.height - 10 + ) + } +} +``` + +## update +The update property is an `optional` property. We can use to computed and store some values we don't want to calculate / mutate everytime a quad is drawn with canvas. + +```js +export const Border = { + props: { + color: 0x00ff00ff, + width: 20 + }, + update() { + this.computed.borderColor = formatRGBAtoString(this.props.color) + }, + render(ctx, quad, renderContext) { + //renders node context(if not called the context won't be drawn) + renderContext() + const borderWidth = this.props.width + ctx.strokeStyle = this.computed.borderColor + ctx.lineWidth = borderWidth + const halfW = borderWidth * 0.5; + ctx.beginPath() + //draw an innerBorder + ctx.strokeRect( + quad.tx + halfW, + quad.ty + halfW, + quad.width - borderWidth, + quad.height - borderWidth + ) + } +} +``` + +## saveAndRestore +Generally when you are about to transform shape, rotation or clip with canvas methods you use `ctx.save` > apply methods > `ctx.restore`. Saving and restoring is very costly if used a lot. To reduce save and restore calls there is a property called `saveAndRestore` you can use to let the renderer know you need it to save and restore before and after this shader is executed. + +```js +export const Rounded = { + render(ctx, quad, renderContext) { + const path = new Path2D(); + roundRect( + path, + quad.tx, + quad.ty, + quad.width, + quad.height, + this.computed.radius!, + ); + ctx.clip(path); + //renders node context(if not called the context won't be drawn) + renderContext() + } +} +``` +See following source to learn more about the Rounded canvas shader. + +You might still want to use `ctx.save` and `ctx.restore` in your render function. But this is only done when you want to more effects on top of a node. Just be careful not to overuse it. diff --git a/docs/shaders/importing-shaders.md b/docs/shaders/importing-shaders.md new file mode 100644 index 00000000..4d4b8bc9 --- /dev/null +++ b/docs/shaders/importing-shaders.md @@ -0,0 +1,63 @@ +# Custom Shaders +Along with the built-in shader attributes Blits provides, you can also create your own shaders, to do this you'll have to define a `ShaderType`. + +## ShaderType basics +A ShaderType is a configuration object the renderer needs to create a shader node and optionally extra programs to make it actually render something on the screen. + +Any basic ShaderType can consist of the following optional properties + +### props +Similar to Blits props, you can use specific values to alter the way an effect is rendered by the renderer. The main difference between the Blits props and ShaderType props is that theses props need a default value, therefor you can only use objects. + +```js +const ShaderType = { + props: { + foo: 1, + fooToo: { + default: 1, + } + } +} +``` + +#### pointer props +You can also use "pointer" props to adjust other props. This is handy in cases you use arrays as value f.e; + +```js +const ShaderType = { + props: { + foo: [0, 0, 0, 0], + fooOne: { + set(v, props) { + props.foo[1] = v; + }, + get(props) { + return props.foo[1] + } + } + } +} +``` + +### getCacheMarkers +In some cases you might want to generate code based on the props you pass when using a specific ShaderType. + +## ShaderTypes for different render modes +The Renderer that Blits uses makes use of different ShaderTypes for each render mode, [webgl](./webgl-shadertypes.md) or [canvas](./canvas-shadertypes.md). Blits(and the Renderer) by default use `webgl` ShaderTypes. + +## Including ShaderTypes to your project +You can register ShaderTypes in the Launch settings of your App (in `src/index.js`). The `shaders`-key in the settings is an `Array` that specifies your custom shaders in your App. + +```js + shaders: [ + // ... + { + name: 'myshader', // shader name - used in Element Attribute + type: MyShaderType, // ShaderType object. + }, + // .. + ], +``` + + + diff --git a/docs/shaders/v2-conversion-guide.md b/docs/shaders/v2-conversion-guide.md new file mode 100644 index 00000000..3f1a30a2 --- /dev/null +++ b/docs/shaders/v2-conversion-guide.md @@ -0,0 +1,177 @@ +# v2 Conversion Guide +This is a conversion guide for changing the use of the `effects` attribute to the new `shader` system introduced in `v2`. + +There has been quite a few changes in order to make the use of shaders less performance heavy. The system the Renderer used to make these effects cost us a lot of performance, and for this reason was discontinued. This comes with bunch of changes for Blits too, we covered most basic uses but some others might require a bit more time to make it work again. This guide helps you getting started with converting the effect attribute to the new built-in attributes. + +Please take a look at the following [link](../essentials/element_attributes.md#shaders) to familiarize yourself with the new attributes. + +We'll start with the easiest conversions, and later on tackle the more complicated conversions. + +## Built-in +Here are some examples on changing common used effects to built-in `shader attributes`. + +### Radius +Radius has been with a `rounded` attribute: + +```xml + + + +``` + +### Border +Border still uses the name `border` and has its own attribute: + +```xml + + + +``` + +### Combining Radius & Border +You can combine built-in attributes: + +```xml + + + +``` + +### linearGradient +To use the `linearGradient` you have to use the `shader` attribute: + +```xml + + + +``` + +### radialGradient +To use the `radialGradient` you have to use the `shader` attribute: + +```xml + + + +``` + +### holePunch +To use the `holePunch` you have to use the `shader` attribute: + +```xml + + + +``` + +## Built-in Advanced +Some combinations that were available in `effects` are not available anymore in the same `Element`. However you can make use of nested elements to recreate some effects. + +To do this we have to make use of the `rtt` attribute available in Blits. This renders everything within an `Element` to a texture. + +> [!WARNING] +> The use of rtt comes with extra performance costs. + +For example if you have a tile with `radius` and an image and a `linearGradient` overlay. You can do the following: + +```xml + + + +``` + +```xml + + + +``` + +You can also nest `shader` attributes: + +```xml + + + +``` + +## Custom Conversion +Don't want to make use of the `rtt` attribute? You can create your own Shader Type to alter your node. You can start by reading into [importing shaders](./importing-shaders.md), and [webgl](./webgl-shadertypes.md) Shader Types. + +Following are a couple examples on how to combine your own shaders: + +### Rounded + Linear Gradient +To create a shader like this, start by getting the [source](https://github.com/lightning-js/renderer/blob/main/src/core/shaders/webgl/Rounded.ts) of the Rounded shader from the Renderer repository. + +You will use this as a base to add the `linearGradient` effect onto. ([source](https://github.com/lightning-js/renderer/blob/main/src/core/shaders/webgl/LinearGradient.ts)) + +For this example we used a fixed amount of color stops to make the example a more readable. + +Now that you have the base code for our shader you can start adding the `linearGradient` related uniforms and functions. + + +```glsl +//...default rounded uniforms, varyings, functions + +//add linearGradient uniforms +//angle in degrees +uniform float u_angle; +uniform float u_stops[3]; +uniform vec4 u_colors[3]; + +vec2 calcPoint(float d, float angle) { + return d * vec2(cos(angle), sin(angle)) + (u_dimensions * 0.5); +} + +vec4 linearGradientColor(vec4 colors[3], float stops[3], float angle) { + //line the gradient follows + float lineDist = abs(u_dimensions.x * cos(angle)) + abs(u_dimensions.y * sin(angle)); + vec2 f = calcPoint(lineDist * 0.5, angle); + vec2 t = calcPoint(lineDist * 0.5, a + PI); + vec2 gradVec = t - f; + float dist = dot(v_textureCoords.xy * u_dimensions - f, gradVec) / dot(gradVec, gradVec); + + float stopCalc = (dist - stops[0]) / (stops[1] - stops[0]); + vec4 colorOut = mix(colors[0], colors[1], stopCalc); + colorOut = mix(colorOut, colors[2], clamp((dist - stops[1]) / (stops[2] - stops[1]), 0.0, 1.0)); + return colorOut; +} + +//...main function +``` + +Now that we've added this, we can start using the `linearGradientColor` in the main function of the fragment shader. We'll focus on this part: + +```glsl +vec4 resColor = vec4(0.0); +resColor = mix(resColor, color, roundedAlpha); +gl_FragColor = resColor * u_alpha; +``` + +What you want to alter is the color value. This contains the color of a Node, or the color of a texture. You want to overlay the gradient color on top of this value: + +```glsl +vec4 gradient = linearGradient(u_colors, u_stops, u_angle); +color = mix(color, gradient, clamp(gradient.a, 0.0, 1.0)); +vec4 resColor = vec4(0.0); +resColor = mix(resColor, color, roundedAlpha); +gl_FragColor = resColor * u_alpha; +``` + +Now you have a shader with a LinearGradient overlay on top of a texture or default color, and rounded corners. + +### Borders + Linear Gradient +Want a border to have a gradient effect? You can use the same principle we used earlier with the rounded version. Checkout the Border [source](https://github.com/lightning-js/renderer/blob/main/src/core/shaders/webgl/Border.ts) and add the LinearGradient stuff you added to the Rounded shader. Once you've added this you can alter the border color from: + +```glsl +vec4 resColor = mix(u_borderColor, color, innerAlpha); +gl_FragColor = resColor * u_alpha; +``` + +to + +```glsl +vec4 gradient = linearGradient(u_colors, u_stops, u_angle); +vec4 bColor = mix(u_borderColor, gradient, clamp(gradient.a, 0.0, 1.0)); +vec4 resColor = mix(bColor, color, innerAlpha); +gl_FragColor = resColor * u_alpha; +``` diff --git a/docs/shaders/webgl-shadertypes.md b/docs/shaders/webgl-shadertypes.md new file mode 100644 index 00000000..c347f748 --- /dev/null +++ b/docs/shaders/webgl-shadertypes.md @@ -0,0 +1,175 @@ +# WebGL ShaderTypes +With WebGL ShaderTypes you can define shaders that work for WebGL renderer. In order to create WebGL ShaderTypes a basic understanding of shaders is required, visit [`The Book of Shader`](https://thebookofshaders.com/01/) to discover more. The following properties are used in a WebGlShaderType: + +## fragment +The fragment property is the only `required` property for the WebGLShaderType. This property needs a fragment shader source. This usually comes in a the form of a string. + +```js +export const Default = { + fragment: ` + # ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + # else + precision mediump float; + # endif + + uniform sampler2D u_texture; + + varying vec4 v_color; + varying vec2 v_textureCoords; + + void main() { + vec4 color = texture2D(u_texture, v_textureCoords); + gl_FragColor = vec4(v_color) * texture2D(u_texture, v_textureCoords); + } + ` +} +``` + +You can also use a function that generates a fragment shader source based on the renderer and the configured props in the ShaderType. + +```js +export const RadialGradient = { + fragment(renderer, props) { + return RadialGradientSource(renderer, props) + } +} +``` +View the actual code for generating the example code [here](https://github.com/lightning-js/renderer/blob/main/src/core/shaders/webgl/RadialGradient.ts). + +### uniforms +You can use `uniform` values to alter what is rendered. There are some uniforms that the renderer passes automatically if they are defined in your fragment source; + +``` +uniform float u_alpha; //alpha of the node +uniform vec2 u_dimensions; //size of the node +uniform sampler2D u_texture; //the texture of the node +uniform vec2 u_resolution; //size of the stage or clipping rect of a parent node +uniform float u_pixelRatio; //pixel ratio used for current calculation +``` + +### varyings +You can also you `varying` values to alter what is drawn, these values are different per pixel. You can create `varying` values in a vertex shader, however the renderer exposes the following `varying` values by default: + +``` +varying vec4 v_color; //the premultiplied color with alpha +varying vec2 v_textureCoords; //textureCoordinates used to draw the texture (usually a value between 0 and 1) +varying vec2 v_nodeCoords; //coordinates within the node. similar to v_textureCoords a value between 0 and 1 +``` + +## vertex +The vertex property is an `optional` property and generally only used if you are making more advanced shaders. With the vertex shaders you can alter the position where the node will be drawn, and you can use `attribute` values to fill `varying` values. + +```js +export const Default = { + vertex: ` + # ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + # else + precision mediump float; + # endif + + attribute vec2 a_position; + attribute vec2 a_textureCoords; + attribute vec4 a_color; + attribute vec2 a_nodeCoords; + + uniform vec2 u_resolution; + uniform float u_pixelRatio; + + varying vec4 v_color; + varying vec2 v_textureCoords; + varying vec2 v_nodeCoords; + + void main() { + vec2 normalized = a_position * u_pixelRatio; + vec2 screenSpace = vec2(2.0 / u_resolution.x, -2.0 / u_resolution.y); + + v_color = a_color; + v_nodeCoords = a_nodeCoords; + v_textureCoords = a_textureCoords; + + gl_Position = vec4(normalized.x * screenSpace.x - 1.0, normalized.y * -abs(screenSpace.y) + 1.0, 0.0, 1.0); + gl_Position.y = -sign(screenSpace.y) * gl_Position.y; + } + ` +} +``` + +If you are interested in a more advanced version of a vertex shader view the following [source](https://github.com/lightning-js/renderer/blob/main/src/core/shaders/webgl/Shadow.ts) + +## update +The update property is an `optional` property. It is generally used to update uniform values that are passed to the `fragment`/`vertex` when the is rendered. + +```js +export const ColorBurn = { + update() { + //this is a WebGLCoreNode only function to make passing colors easier + this.uniformRGBA('u_color', 0x00ff00ff); + }, + fragment: ` + # ifdef GL_FRAGMENT_PRECISION_HIGH + precision highp float; + # else + precision mediump float; + # endif + + uniform sampler2D u_texture; + + varying vec4 v_color; + varying vec2 v_textureCoords; + + uniform vec4 u_color; + + void main() { + vec4 color = texture2D(u_texture, v_textureCoords); + gl_FragColor = 1.0 - (1.0 - texture) / u_color; + } + ` +} +``` + +If you are using props you can access these inside the update function: + +```js +{ + props: { + color: 0x00ff00ff, + }, + update() { + //this is a WebGLCoreNode only function to make passing colors easier + this.uniformRGBA('u_color', this.props.color) + } +} +``` + +You can also get information about the node during the update function: + +```js +{ + update(node) { + //creates a vec2 uniform + this.uniform2f('u_halfSize', node.width / 2, node.height / 2) + } +} +``` + +## canBatch +Generally the Renderer checks if the `Quad` that is supposed to be drawn can use the current shader with the loaded uniforms, we call this a `batch check`. +The Renderer has functions it runs by default to check if a shader can be batched or not, however with the `canBatch` property you can configure a function that compares two quads for difference. + +> Note: The `canBatch` function overwrites default Renderer batch checks. + +```js +{ + props: { + size: 0, + }, + canBatch(targetQuad, currentQuad) { + if(targetQuad.width !== currentQuad.width) { + return false + } + return true + } +} +``` diff --git a/docs/sidebar.json b/docs/sidebar.json index 81048dbf..7d4c4183 100644 --- a/docs/sidebar.json +++ b/docs/sidebar.json @@ -125,6 +125,27 @@ } ] }, + { + "text": "Shader", + "items": [ + { + "text": "Importing Shaders", + "link": "/shaders/importing-shaders" + }, + { + "text": "WebGl Shaders", + "link": "/built-in/webgl-shadertypes" + }, + { + "text": "Canvase Shaders", + "link": "/built-in/webgl-shadertypes" + }, + { + "text": "v2 Conversion Guide", + "link": "/built-in/v2-conversion-guide" + } + ] + }, { "text": "Plugins", "items": [ diff --git a/index.d.ts b/index.d.ts index bb44c645..17c060ab 100644 --- a/index.d.ts +++ b/index.d.ts @@ -18,7 +18,9 @@ // blits file type reference /// -import {type ShaderEffect as RendererShaderEffect, type WebGlCoreShader, type RendererMainSettings} from '@lightningjs/renderer' +import {type ShaderEffect as RendererShaderEffect, type RendererMainSettings} from '@lightningjs/renderer' +import { CanvasShaderType } from '@lightningjs/renderer/canvas'; +import { WebGlShaderType } from '@lightningjs/renderer/webgl'; declare module '@lightningjs/blits' { @@ -31,6 +33,11 @@ declare module '@lightningjs/blits' { * Does not interupt the message when it's already being announced. */ cancel() + /** + * Interrupts a specific message as it is being spoken out by the Text to Speech + * engine. + */ + stop() } export interface Announcer { @@ -57,7 +64,7 @@ declare module '@lightningjs/blits' { /** * Clears out the announcement queue of messages. */ - clears(): void; + clear(): void; /** * Enables the announcer. */ @@ -707,7 +714,7 @@ declare module '@lightningjs/blits' { type Shader = { name: string, - type: WebGlCoreShader + type: WebGlShaderType | CanvasShaderType } type ScreenResolutions = 'hd' | '720p' | 720 | 'fhd' | 'fullhd' | '1080p' | 1080 | '4k' | '2160p' | 2160 @@ -743,10 +750,6 @@ declare module '@lightningjs/blits' { * Fonts to be used in the Application */ fonts?: Font[], - /** - * Effects to be used by DynamicShader - */ - effects?: ShaderEffect[], /** * Shaders to be used in the application */ diff --git a/package-lock.json b/package-lock.json index 42d91f02..08a4ef6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,16 @@ { "name": "@lightningjs/blits", - "version": "1.26.0", + "version": "1.27.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@lightningjs/blits", - "version": "1.26.0", + "version": "1.27.1", "license": "Apache-2.0", "dependencies": { "@lightningjs/msdf-generator": "^1.1.1", - "@lightningjs/renderer": "^2.13.2" + "@lightningjs/renderer": "^3.0.0-beta5" }, "bin": { "blits": "bin/index.js" @@ -18,15 +18,14 @@ "devDependencies": { "@babel/eslint-parser": "^7.26.5", "@babel/plugin-syntax-import-assertions": "^7.26.0", - "babel-eslint": "^10.1.0", "c8": "^8.0.1", "eslint": "^8.8.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "global-jsdom": "24.0.0", - "husky": "^7.0.4", + "husky": "^9.1.7", "jsdom": "24.0.0", - "lint-staged": "^12.3.3", + "lint-staged": "^15.5.0", "prettier": "^2.5.1", "tap-diff": "^0.1.1", "tape": "^5.5.0" @@ -53,6 +52,7 @@ "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "js-tokens": "^4.0.0", @@ -117,9 +117,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.5.tgz", - "integrity": "sha512-Kkm8C8uxI842AwQADxl0GbcG1rupELYLShazYEZO/2DYjhyWXJIOUVOE3tBYm6JXzUCNJOZEzqc4rCW/jsEQYQ==", + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.27.0.tgz", + "integrity": "sha512-dtnzmSjXfgL/HDgMcmsLSzyGbEosi4DrGWoCNfuI+W4IkVJw6izpTe7LtOdwAXnkDqw5yweboYCTkM2rQizCng==", "dev": true, "license": "MIT", "dependencies": { @@ -161,6 +161,7 @@ "integrity": "sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.26.5", "@babel/types": "^7.26.5", @@ -251,6 +252,7 @@ "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -261,6 +263,7 @@ "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=6.9.0" } @@ -297,6 +300,7 @@ "integrity": "sha512-SRJ4jYmXRqV1/Xc+TIVG84WjHBXKlxO9sHQnA2Pf12QQEAp1LOh6kDzNHXcUnbH1QI0FDoPPVOt+vyUDucxpaw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/types": "^7.26.5" }, @@ -339,6 +343,7 @@ "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.25.9", "@babel/parser": "^7.25.9", @@ -354,6 +359,7 @@ "integrity": "sha512-rkOSPOw+AXbgtwUga3U4u8RpoK9FEFWBNAlTpcnkLFjL5CT+oyHNuUUC/xx6XefEJ16r38r8Bc/lfp6rYuHeJQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.26.2", "@babel/generator": "^7.26.5", @@ -373,6 +379,7 @@ "integrity": "sha512-L6mZmwFDK6Cjh1nRCLXpa6no13ZIioJDz7mdkzHv399pThrTa/k0nUlNaenOeh2kWu/iaOQYElEpKPUswUa9Vg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" @@ -892,6 +899,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", "dev": true, + "peer": true, "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", @@ -915,6 +923,7 @@ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true, + "peer": true, "engines": { "node": ">=6.0.0" } @@ -950,13 +959,11 @@ } }, "node_modules/@lightningjs/renderer": { - "version": "2.13.2", - "resolved": "https://registry.npmjs.org/@lightningjs/renderer/-/renderer-2.13.2.tgz", - "integrity": "sha512-IUh9OFEpUk0cnnLVw6OqW2m+CzmT2aOkLFzxuErC2xajhDKMRBCMe+kM+W26zqnc7SMSkkW3zs10oAKnrbzPsA==", - "hasInstallScript": true, - "license": "Apache-2.0", + "version": "3.0.0-beta6", + "resolved": "https://registry.npmjs.org/@lightningjs/renderer/-/renderer-3.0.0-beta6.tgz", + "integrity": "sha512-1spHTpZ8CLgL9ZZNtvKdaJYazn+SM4neIOj5wb8jJ6aFBbj4CyAeigyNmhYylo0lUr9VxfmXoJ4n6efUm2p8Vg==", "engines": { - "node": ">= 20.9.0", + "node": ">= 18.0.0", "npm": ">= 10.0.0", "pnpm": ">= 8.9.2" } @@ -1119,19 +1126,6 @@ "node": ">= 14" } }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1183,15 +1177,16 @@ } }, "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==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", "dev": true, + "license": "MIT", "dependencies": { - "type-fest": "^0.21.3" + "environment": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1210,6 +1205,7 @@ "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" }, @@ -1290,15 +1286,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -1320,27 +1307,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "deprecated": "babel-eslint is now @babel/eslint-parser. This package will no longer receive updates.", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "eslint": ">= 4.12.1" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1489,6 +1455,7 @@ "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" }, @@ -1709,9 +1676,10 @@ } }, "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -1724,15 +1692,6 @@ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==" }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/cli-boxes": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-2.2.1.tgz", @@ -1745,15 +1704,19 @@ } }, "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { - "restore-cursor": "^3.1.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-progress": { @@ -1794,16 +1757,17 @@ } }, "node_modules/cli-truncate": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-3.1.0.tgz", - "integrity": "sha512-wfOBkjXteqSnI59oPcJkcPl/ZmwvMMOj340qUIY1SKZCv0B9Cf4D4fAucRkIKQmsIuYK3x1rrgU7MeGRruiuiA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", "dev": true, + "license": "MIT", "dependencies": { "slice-ansi": "^5.0.0", - "string-width": "^5.0.0" + "string-width": "^7.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1867,7 +1831,8 @@ "version": "2.0.20", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -1882,12 +1847,13 @@ } }, "node_modules/commander": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", - "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", + "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.20.0 || >=14" + "node": ">=18" } }, "node_modules/concat-map": { @@ -2058,10 +2024,11 @@ } }, "node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -2260,12 +2227,6 @@ "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==" }, - "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 - }, "node_modules/electron-to-chromium": { "version": "1.5.84", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.84.tgz", @@ -2275,10 +2236,11 @@ "peer": true }, "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 + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", + "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "dev": true, + "license": "MIT" }, "node_modules/end-of-stream": { "version": "1.4.4", @@ -2308,6 +2270,19 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/es-abstract": { "version": "1.23.3", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", @@ -2491,7 +2466,9 @@ "version": "8.57.1", "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", @@ -2591,15 +2568,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2772,6 +2740,13 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, "node_modules/events-to-array": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/events-to-array/-/events-to-array-1.1.2.tgz", @@ -2779,28 +2754,42 @@ "dev": true }, "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", "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", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.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" + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" }, "engines": { - "node": ">=10" + "node": ">=16.17" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/execa/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/exif-parser": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", @@ -2886,6 +2875,7 @@ "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" }, @@ -3059,6 +3049,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", + "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", @@ -3088,12 +3091,13 @@ } }, "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==", + "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": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3189,6 +3193,7 @@ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", "dev": true, + "peer": true, "engines": { "node": ">=4" } @@ -3459,24 +3464,26 @@ } }, "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==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=10.17.0" + "node": ">=16.17.0" } }, "node_modules/husky": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/husky/-/husky-7.0.4.tgz", - "integrity": "sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ==", + "version": "9.1.7", + "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", + "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "dev": true, + "license": "MIT", "bin": { - "husky": "lib/bin.js" + "husky": "bin.js" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -3554,15 +3561,6 @@ "node": ">=0.8.19" } }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -3756,6 +3754,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -3843,6 +3842,7 @@ "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" } @@ -3928,12 +3928,13 @@ } }, "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==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4104,7 +4105,8 @@ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/js-yaml": { "version": "4.1.0", @@ -4171,6 +4173,7 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "dev": true, + "peer": true, "bin": { "jsesc": "bin/jsesc" }, @@ -4255,174 +4258,109 @@ } }, "node_modules/lilconfig": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz", - "integrity": "sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lint-staged": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-12.5.0.tgz", - "integrity": "sha512-BKLUjWDsKquV/JuIcoQW4MSAI3ggwEImF1+sB4zaKvyVx1wBk3FsG7UK9bpnmBTN1pm7EH2BBcMwINJzCRv12g==", - "dev": true, - "dependencies": { - "cli-truncate": "^3.1.0", - "colorette": "^2.0.16", - "commander": "^9.3.0", - "debug": "^4.3.4", - "execa": "^5.1.1", - "lilconfig": "2.0.5", - "listr2": "^4.0.5", - "micromatch": "^4.0.5", - "normalize-path": "^3.0.0", - "object-inspect": "^1.12.2", - "pidtree": "^0.5.0", - "string-argv": "^0.3.1", - "supports-color": "^9.2.2", - "yaml": "^1.10.2" + "version": "15.5.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.0.tgz", + "integrity": "sha512-WyCzSbfYGhK7cU+UuDDkzUiytbfbi0ZdPy2orwtM75P3WTtQBzmG40cCxIa8Ii2+XjfxzLH6Be46tUfWS85Xfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^5.4.1", + "commander": "^13.1.0", + "debug": "^4.4.0", + "execa": "^8.0.1", + "lilconfig": "^3.1.3", + "listr2": "^8.2.5", + "micromatch": "^4.0.8", + "pidtree": "^0.6.0", + "string-argv": "^0.3.2", + "yaml": "^2.7.0" }, "bin": { "lint-staged": "bin/lint-staged.js" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18.12.0" }, "funding": { "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/listr2": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz", - "integrity": "sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA==", - "dev": true, - "dependencies": { - "cli-truncate": "^2.1.0", - "colorette": "^2.0.16", - "log-update": "^4.0.0", - "p-map": "^4.0.0", - "rfdc": "^1.3.0", - "rxjs": "^7.5.5", - "through": "^2.3.8", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "enquirer": ">= 2.3.0 < 3" - }, - "peerDependenciesMeta": { - "enquirer": { - "optional": true - } - } - }, - "node_modules/listr2/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==", + "version": "8.2.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", + "integrity": "sha512-iyAZCeyD+c1gPyE9qpFu8af0Y+MRtmKOncdGoA2S5EY8iFq99dmmvkNnHiWo+pj0s7yH7l3KPIgee77tKpXPWQ==", "dev": true, + "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=18.0.0" } }, - "node_modules/listr2/node_modules/cli-truncate": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", - "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", + "node_modules/listr2/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, - "dependencies": { - "slice-ansi": "^3.0.0", - "string-width": "^4.2.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/listr2/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==", + "node_modules/listr2/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": { - "color-name": "~1.1.4" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/listr2/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 - }, - "node_modules/listr2/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 - }, - "node_modules/listr2/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/listr2/node_modules/slice-ansi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", - "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "node": ">=12" }, - "engines": { - "node": ">=8" + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/listr2/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==", + "node_modules/listr2/node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/load-bmfont": { @@ -4473,114 +4411,103 @@ "dev": true }, "node_modules/log-update": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", - "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-escapes": "^4.3.0", - "cli-cursor": "^3.1.0", - "slice-ansi": "^4.0.0", - "wrap-ansi": "^6.2.0" + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/log-update/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==", + "node_modules/log-update/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, - "dependencies": { - "color-convert": "^2.0.1" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/log-update/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==", + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", "dev": true, + "license": "MIT", "dependencies": { - "color-name": "~1.1.4" + "get-east-asian-width": "^1.0.0" }, "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-update/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 - }, - "node_modules/log-update/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 - }, - "node_modules/log-update/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, - "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/log-update/node_modules/slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, - "node_modules/log-update/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==", + "node_modules/log-update/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": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "ansi-regex": "^6.0.1" }, "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, "node_modules/log-update/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==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, "node_modules/lowercase-keys": { @@ -4634,13 +4561,15 @@ "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 + "dev": true, + "license": "MIT" }, "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" @@ -4682,12 +4611,29 @@ } }, "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==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mimic-response": { @@ -4833,15 +4779,6 @@ "license": "MIT", "peer": true }, - "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, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/normalize-url": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", @@ -4851,15 +4788,32 @@ } }, "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==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^3.0.0" + "path-key": "^4.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/nwsapi": { @@ -4946,15 +4900,16 @@ } }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-fn": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5030,21 +4985,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/package-json": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", @@ -5173,13 +5113,15 @@ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, - "license": "ISC" + "license": "ISC", + "peer": true }, "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" }, @@ -5188,10 +5130,11 @@ } }, "node_modules/pidtree": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.5.0.tgz", - "integrity": "sha512-9nxspIM7OpZuhBxPg73Zvyq7j1QMPMPsGKTqRc2XOaFQauDvoNz9fM1Wdkjmeo7l9GXOZiRs97sPkuayl39wjA==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", "dev": true, + "license": "MIT", "bin": { "pidtree": "bin/pidtree.js" }, @@ -5471,23 +5414,6 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, - "node_modules/resolve": { - "version": "1.22.8", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", - "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", - "dev": true, - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -5506,16 +5432,49 @@ } }, "node_modules/restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": ">=8" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/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/reusify": { @@ -5532,7 +5491,8 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/rimraf": { "version": "3.0.2", @@ -5579,15 +5539,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/rxjs": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", - "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", - "dev": true, - "dependencies": { - "tslib": "^2.1.0" - } - }, "node_modules/safe-array-concat": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", @@ -5769,6 +5720,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.0.0", "is-fullwidth-code-point": "^4.0.0" @@ -5820,22 +5772,24 @@ "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.6.19" } }, "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==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, + "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^9.2.2", - "strip-ansi": "^7.0.1" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5846,6 +5800,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -5858,6 +5813,7 @@ "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" }, @@ -5934,12 +5890,16 @@ } }, "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==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/strip-json-comments": { @@ -6177,12 +6137,6 @@ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -6221,6 +6175,7 @@ "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" }, @@ -6264,12 +6219,6 @@ "node": ">=18" } }, - "node_modules/tslib": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "dev": true - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6282,18 +6231,6 @@ "node": ">= 0.8.0" } }, - "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, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typed-array-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", @@ -6956,12 +6893,16 @@ "peer": true }, "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.1.tgz", + "integrity": "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==", "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 9aecc2b1..7812751e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@lightningjs/blits", - "version": "1.26.0", + "version": "1.27.1", "description": "Blits: The Lightning 3 App Development Framework", "bin": "bin/index.js", "exports": { @@ -18,7 +18,8 @@ "lint": "eslint '**/*.js'", "lint:fix": "eslint '**/*.js' --fix", "prepublishOnly": "node scripts/prepublishOnly.js", - "postpublish": "node scripts/postpublish.js" + "postpublish": "node scripts/postpublish.js", + "prepare": "husky" }, "types": "./index.d.ts", "lint-staged": { @@ -26,33 +27,27 @@ "eslint --fix" ] }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, "type": "module", "author": "Michiel van der Geest ", "license": "Apache-2.0", "devDependencies": { "@babel/eslint-parser": "^7.26.5", "@babel/plugin-syntax-import-assertions": "^7.26.0", - "babel-eslint": "^10.1.0", "c8": "^8.0.1", "eslint": "^8.8.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", "global-jsdom": "24.0.0", - "husky": "^7.0.4", + "husky": "^9.1.7", "jsdom": "24.0.0", - "lint-staged": "^12.3.3", + "lint-staged": "^15.5.0", "prettier": "^2.5.1", "tap-diff": "^0.1.1", "tape": "^5.5.0" }, "dependencies": { "@lightningjs/msdf-generator": "^1.1.1", - "@lightningjs/renderer": "^2.13.2" + "@lightningjs/renderer": "^3.0.0-beta5" }, "repository": { "type": "git", diff --git a/packages/create-blits/.eslintrc.cjs b/packages/create-blits/.eslintrc.cjs index 79476d94..792d6778 100644 --- a/packages/create-blits/.eslintrc.cjs +++ b/packages/create-blits/.eslintrc.cjs @@ -52,7 +52,7 @@ module.exports = { }, parserOptions: { parser: 'babel-eslint', - ecmaVersion: 2018, + ecmaVersion: 2020, sourceType: 'module', }, globals: { diff --git a/packages/create-blits/boilerplate/js/default/src/pages/Home.js b/packages/create-blits/boilerplate/js/default/src/pages/Home.js index 6d468052..873c8250 100644 --- a/packages/create-blits/boilerplate/js/default/src/pages/Home.js +++ b/packages/create-blits/boilerplate/js/default/src/pages/Home.js @@ -20,7 +20,7 @@ export default Blits.Component('Home', { :x.transition="{value: $x, delay: 200, duration: 1200, easing: 'cubic-bezier(1,-0.64,.39,1.44)'}" mount="{x: 0.5}" y="320" - :effects="[$shader('radius', {radius: 8})]" + rounded="8" /> diff --git a/packages/create-blits/boilerplate/ts/default/src/pages/Home.ts b/packages/create-blits/boilerplate/ts/default/src/pages/Home.ts index f2829546..77f86bf5 100644 --- a/packages/create-blits/boilerplate/ts/default/src/pages/Home.ts +++ b/packages/create-blits/boilerplate/ts/default/src/pages/Home.ts @@ -20,7 +20,7 @@ export default Blits.Component('Home', { :x.transition="{value: $x, delay: 200, duration: 1200, easing: 'cubic-bezier(1,-0.64,.39,1.44)'}" mount="{x: 0.5}" y="320" - :effects="[$shader('radius', {radius: 8})]" + rounded="8" /> diff --git a/packages/create-blits/src/index.js b/packages/create-blits/src/index.js index a6cf513a..4ae2d6c4 100644 --- a/packages/create-blits/src/index.js +++ b/packages/create-blits/src/index.js @@ -13,7 +13,8 @@ import { setAppData, setBlitsVersion, gitInit, - done, spinnerMsg + done, + spinnerMsg, } from './helpers/create.js' const defaultBanner = ` @@ -29,19 +30,17 @@ console.log(defaultBanner) const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) -const fixturesBase = path.join( - __dirname, - '../boilerplate') +const fixturesBase = path.join(__dirname, '../boilerplate') const questions = [ { type: 'text', name: 'appName', message: 'What is the name of your App?', - format: val => { + format: (val) => { // Check if the provided application name is empty if (!val.trim()) { - spinnerMsg.fail(red(bold("Please provide a name for the App"))) + spinnerMsg.fail(red(bold('Please provide a name for the App'))) return process.exit(1) } else { return val @@ -53,16 +52,17 @@ const questions = [ type: 'text', name: 'appPackage', message: 'What is the package name of your App?', - format: val => { + format: (val) => { // Validate the package name using validate-npm-package-name if (!validatePackage(val).validForNewPackages) { - spinnerMsg.fail(red(bold("Please provide a valid package name"))) + spinnerMsg.fail(red(bold('Please provide a valid package name'))) return process.exit(1) } else { return val } }, - initial: prev => `${prev.replace(/[\sA-Z]/g, str => str === ' ' ? '-' : str.toLowerCase())}`, + initial: (prev) => + `${prev.replace(/[\sA-Z]/g, (str) => (str === ' ' ? '-' : str.toLowerCase()))}`, }, { type: 'text', @@ -82,7 +82,9 @@ const questions = [ } catch (e) { // Handle case where an error occurred during file system interaction if (e.code === 'ENOENT') { - spinnerMsg.fail(red(bold("Entered directory path is invalid, please enter a valid directory!"))) + spinnerMsg.fail( + red(bold('Entered directory path is invalid, please enter a valid directory!')) + ) process.exit() } } @@ -97,10 +99,14 @@ const questions = [ name: 'projectType', message: 'What kind of project do you want to create?', choices: [ - { title: 'Javascript', description: 'JS based project (with JSDoc for type checking and autocompletion)', value: 'js' }, + { + title: 'Javascript', + description: 'JS based project (with JSDoc for type checking and autocompletion)', + value: 'js', + }, { title: 'TypeScript', description: 'TS based project', value: 'ts' }, ], - initial: 0 + initial: 0, }, { type: 'toggle', @@ -120,9 +126,8 @@ const questions = [ }, ] - const createApp = () => { - return new Promise(resolve => { + return new Promise((resolve) => { let config sequence([ async () => { @@ -135,14 +140,16 @@ const createApp = () => { }, () => { spinnerMsg.start(`Generating new App "${config.appName}" ...`) - copyLightningFixtures(config).then(targetDir => (config.targetDir = targetDir)).catch(console.error) + copyLightningFixtures(config) + .then((targetDir) => (config.targetDir = targetDir)) + .catch(console.error) spinnerMsg.succeed() }, () => setAppData(config), () => setBlitsVersion(config), () => config.esLint && addESlint(config), () => config.gitInit && gitInit(config.targetDir, config.fixturesBase), - () => done(config) + () => done(config), ]) }) } diff --git a/scripts/prepush.js b/scripts/prepush.js new file mode 100644 index 00000000..5c09b4fd --- /dev/null +++ b/scripts/prepush.js @@ -0,0 +1,78 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2023 Comcast + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { execSync } from 'child_process' +import { readFileSync } from 'fs' + +// Read the input provided by the pre-push hook +const input = readFileSync(0, 'utf-8').trim().split(' ') +if (input.length < 4) { + console.log('Insufficient input from pre-push hook. Skipping lint.') + process.exit(0) +} + +const localSha = input[1] // The SHA being pushed +const remoteSha = input[3] // The remote SHA, can be zeros for new branch + +// If remote is all zeros, it's a new branch +const isNewBranch = remoteSha === '0000000000000000000000000000000000000000' + +let filesToLint + +try { + if (isNewBranch) { + // For a new branch, compare with dev + filesToLint = execSync(`git diff --name-only dev...${localSha} | grep '\\.js$'`, { + encoding: 'utf8', + }) + } else { + // Only check files in the commits that are being pushed right now + filesToLint = execSync(`git diff --name-only ${remoteSha}..${localSha} | grep '\\.js$'`, { + encoding: 'utf8', + }) + } +} catch (error) { + // grep will exit with code 1 if it doesn't find anything + console.log('No JavaScript files changed. Nothing to lint.') + process.exit(0) +} + +if (!filesToLint.trim()) { + console.log('No JavaScript files changed. Nothing to lint.') + process.exit(0) +} + +const fileList = filesToLint.trim().split('\n') +console.log(`Running ESLint on ${fileList.length} changed JavaScript files:\n`) +console.log(filesToLint) + +try { + // Run ESLint on the changed files + execSync(`npx eslint ${fileList.join(' ')}`, { stdio: 'inherit' }) + console.log('Linting passed successfully!\n') + process.exit(0) +} catch (error) { + console.log('\n==========================================================================') + console.log('ESLint found issues in your code. Please fix them before pushing.') + console.log('You can run:\n') + console.log('npx eslint --fix "**/*.js"\n') + console.log('on the specific files to automatically fix some issues.') + console.log('==========================================================================\n') + process.exit(1) +} diff --git a/src/announcer/announcer.js b/src/announcer/announcer.js index 6b44e930..64ec0902 100644 --- a/src/announcer/announcer.js +++ b/src/announcer/announcer.js @@ -21,6 +21,15 @@ let active = false let count = 0 const queue = [] let isProcessing = false +let currentId = null +let debounce = null + +const noopAnnouncement = { + then() {}, + done() {}, + cancel() {}, + stop() {}, +} const enable = () => { active = true @@ -35,13 +44,13 @@ const toggle = (v) => { } const speak = (message, politeness = 'off') => { - if (active === false) return + if (active === false) return noopAnnouncement return addToQueue(message, politeness) } const pause = (delay) => { - if (active === false) return + if (active === false) return noopAnnouncement return addToQueue(undefined, undefined, delay) } @@ -64,6 +73,15 @@ const addToQueue = (message, politeness, delay = false) => { resolveFn('canceled') } + // augment the promise with a stop function + done.stop = () => { + if (id === currentId) { + speechSynthesis.cancel() + isProcessing = false + resolveFn('interupted') + } + } + // add message of pause if (delay === false) { politeness === 'assertive' @@ -84,27 +102,38 @@ const processQueue = async () => { if (isProcessing === true || queue.length === 0) return isProcessing = true - const { message, resolveFn, delay } = queue.shift() + const { message, resolveFn, delay, id } = queue.shift() + + currentId = id - if (delay !== false) { + if (delay) { setTimeout(() => { isProcessing = false + currentId = null resolveFn('finished') processQueue() }, delay) - return + } else { + if (debounce !== null) clearTimeout(debounce) + // add some easing when speaking the messages to reduce stuttering + debounce = setTimeout(() => { + speechSynthesis + .speak({ message }) + .then(() => { + isProcessing = false + currentId = null + resolveFn('finished') + processQueue() + }) + .catch((e) => { + isProcessing = false + currentId = null + resolveFn(e.error) + processQueue() + }) + debounce = null + }, 200) } - speechSynthesis - .speak({ message }) - .then(() => { - isProcessing = false - resolveFn('finished') - processQueue() - }) - .catch((e) => { - isProcessing = false - resolveFn(e.error) - }) } const polite = (message) => speak(message, 'polite') diff --git a/src/component.js b/src/component.js index e34ba065..4e468953 100644 --- a/src/component.js +++ b/src/component.js @@ -195,13 +195,17 @@ const Component = (name = required('name'), config = required('config')) => { let old = this[key] - effect((force = false) => { + const eff = (force = false) => { const newValue = target[key] if (old !== newValue || force === true) { this[symbols.watchers][watchKey].apply(this, [newValue, old]) old = newValue } - }) + } + + // store reference to the effect + this[symbols.effects].push(eff) + effect(eff) } } diff --git a/src/component/base/methods.js b/src/component/base/methods.js index a44bb6cf..329e066d 100644 --- a/src/component/base/methods.js +++ b/src/component/base/methods.js @@ -58,9 +58,10 @@ export default { eventListeners.removeListeners(this) deleteChildren(this[symbols.children]) removeGlobalEffects(this[symbols.effects]) - Log.debug(`Destroyed component ${this.componentId}`) this[symbols.state] = {} this[symbols.props] = {} + this[symbols.effects].length = 0 + Log.debug(`Destroyed component ${this.componentId}`) }, writable: false, enumerable: true, @@ -132,27 +133,6 @@ export default { enumerable: true, configurable: false, }, - shader: { - value: function (type, args) { - return { - type: type, - props: args, - } - // const shaders = renderer.driver.stage.shManager.getRegisteredEffects() - - // if (target in shaders) { - // return { - // type: target, - // props: args, - // } - // } else { - // Log.error(`Shader ${type} not found`) - // } - }, - writable: false, - enumerable: true, - configurable: false, - }, } const deleteChildren = function (children) { diff --git a/src/components/RouterView.js b/src/components/RouterView.js index 29e73dd5..2ec48f15 100644 --- a/src/components/RouterView.js +++ b/src/components/RouterView.js @@ -35,7 +35,6 @@ export default () => hashchangeHandler = () => Router.navigate.apply(this) Router.navigate.apply(this) window.addEventListener('hashchange', hashchangeHandler) - }, destroy() { window.removeEventListener('hashchange', hashchangeHandler, false) diff --git a/src/engines/L3/element.js b/src/engines/L3/element.js index df02c501..411721a5 100644 --- a/src/engines/L3/element.js +++ b/src/engines/L3/element.js @@ -16,11 +16,13 @@ */ import { renderer } from './launch.js' +import { parseToObject, isObjectString, isArrayString, isTransition } from '../../lib/utils.js' import colors from '../../lib/colors/colors.js' import { Log } from '../../lib/log.js' import symbols from '../../lib/symbols.js' import Settings from '../../settings.js' +import shaders from '../../lib/shaders/shaders.js' // temporary counter to work around shader caching issues let counter = 0 @@ -128,18 +130,11 @@ const layoutFn = function (config) { if (config['@updated'] !== undefined) { config['@updated']({ w: this.node.width, h: this.node.height }, this) } -} - -const isTransition = (value) => { - return value !== null && typeof value === 'object' && 'transition' in value === true -} - -const isObjectString = (str) => { - return typeof str === 'string' && str.startsWith('{') && str.endsWith('}') -} -const parseToObject = (str) => { - return JSON.parse(str.replace(/'/g, '"').replace(/([\w-_]+)\s*:/g, '"$1":')) + // trigger layout on parent if parent is a layout + if (this.config.parent && this.config.parent.props.__layout === true) { + this.config.parent.triggerLayout(this.config.parent.props) + } } const parsePercentage = function (v, base) { @@ -315,48 +310,68 @@ const propsTransformer = { this.props['alpha'] = v } }, - set shader(v) { - if (v !== null) { - this.props['shader'] = renderer.createShader(v.type, v.props) - } else { - this.props['shader'] = renderer.createShader('DefaultShader') + set rounded(v) { + this.props['rounded'] = v + if (this.element.node !== undefined && this.elementShader === true) { + if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) { + this.element.node.props['shader'].props = v + } else { + if (isArrayString(v) === true) { + v = JSON.parse(v) + } + this.element.node.props['shader'].props.radius = v + } } }, - set effects(v) { - for (let i = 0; i < v.length; i++) { - if (v[i].props && v[i].props.color) { - v[i].props.color = colors.normalize(v[i].props.color) + set border(v) { + this.props['border'] = v + if ( + this.element.node !== undefined && + this.elementShader === true && + (typeof v === 'object' || isObjectString(v) === true) + ) { + v = shaders.parseProps(v) + for (const key in v) { + this.element.node.props['shader'].props[`border-${key}`] = v[key] } } - const effectNames = {} - if (this.element.node === undefined) { - this.element.effectNames = [] - this.props['shader'] = renderer.createShader('DynamicShader', { - effects: v.map((effect) => { - let name = effect.type - if (effectNames[name] !== undefined) { - name += ++effectNames[name] - } else { - effectNames[name] = 1 - } - name += this.element.counter - this.element.effectNames.push(name) - // temporary add counter to work around shader caching issues - return renderer.createEffect(effect.type, effect.props, name) - }), - }) - } else { - for (let i = 0; i < v.length; i++) { - const name = this.element.effectNames[i] - // temporary add counter to work around shader caching issues - const target = this.element.node.shader.props[name] - const props = Object.keys(v[i].props) - if (target == undefined) continue - for (let j = 0; j < props.length; j++) { - target[props[j]] = v[i].props[props[j]] - } + }, + set shadow(v) { + this.props['shadow'] = v + if ( + this.element.node !== undefined && + this.elementShader === true && + (typeof v === 'object' || isObjectString(v) === true) + ) { + v = shaders.parseProps(v) + for (const key in v) { + this.element.node.props['shader'].props[`shadow-${key}`] = v[key] + } + } + }, + set shader(v) { + let type = v + if (typeof v === 'object' || (isObjectString(v) === true && (v = parseToObject(v)))) { + type = v.type + v = shaders.parseProps(v) + } + const target = this.element.node !== undefined ? this.element.node : this.props + //if v remains a string we can change shader types + if (typeof v === 'string') { + target['shader'] = renderer.createShader(type) + return + } + + //check again if v is an object since it could have been an object string + if (typeof v === 'object') { + if (target.shader !== undefined && type === target.shader.shaderKey) { + target['shader'].props = v + return } + target['shader'] = renderer.createShader(type, v) + return } + target['shader'] = renderer.createShader('DefaultShader') }, set clipping(v) { this.props['clipping'] = v @@ -443,8 +458,7 @@ const propsTransformer = { } const Element = { - populate(data) { - const props = data + populate(props) { props['node'] = this.config.node if (props[symbols.isSlot] === true) { @@ -456,11 +470,22 @@ const Element = { this.props['parent'] = props['parent'] || this.config.parent delete props.parent - this.props.raw = data + this.props.raw = props + this.props.elementShader = false const propKeys = Object.keys(props) const length = propKeys.length + if ( + props['shader'] === undefined && + (props['rounded'] !== undefined || + props['border'] !== undefined || + props['shadow'] !== undefined) + ) { + this.props.elementShader = true + this.props.props['shader'] = shaders.createElementShader(props) + } + for (let i = 0; i < length; i++) { const key = propKeys[i] const value = props[key] diff --git a/src/engines/L3/fontLoader.js b/src/engines/L3/fontLoader.js index 5b051ef3..7c2bba22 100644 --- a/src/engines/L3/fontLoader.js +++ b/src/engines/L3/fontLoader.js @@ -4,8 +4,9 @@ import { renderer } from './launch.js' export default () => { const stage = renderer.stage + const renderMode = Settings.get('renderMode') Settings.get('fonts', []).forEach((font) => { - if (font.type === 'sdf' || font.type === 'msdf') { + if (renderMode !== 'canvas' && (font.type === 'sdf' || font.type === 'msdf')) { // automatically map png key to file name if (!font.png && font.file) { font.png = font.file.replace(/\.[^.]+$/, `.${font.type}.png`) diff --git a/src/engines/L3/launch.js b/src/engines/L3/launch.js index 70a81a65..162e0575 100644 --- a/src/engines/L3/launch.js +++ b/src/engines/L3/launch.js @@ -63,7 +63,7 @@ const textureMemorySettings = (settings) => { } } -export default (App, target, settings = {}) => { +export default async (App, target, settings = {}) => { renderer = new RendererMain( { ...{ @@ -106,7 +106,7 @@ export default (App, target, settings = {}) => { } } - shaderLoader() + await shaderLoader() fontLoader() initApp() diff --git a/src/engines/L3/shaderLoader.js b/src/engines/L3/shaderLoader.js index e981c9cf..778528fc 100644 --- a/src/engines/L3/shaderLoader.js +++ b/src/engines/L3/shaderLoader.js @@ -1,12 +1,68 @@ import Settings from '../../settings.js' import { renderer } from './launch.js' -export default () => { +function registerBlitsDefaultShaders( + shManager, + Rounded, + Border, + Shadow, + RoundedWithBorder, + RoundedWithShadow, + RoundedWithBorderAndShadow, + HolePunch, + RadialGradient, + LinearGradient +) { + shManager.registerShaderType('rounded', Rounded) + shManager.registerShaderType('border', Border) + shManager.registerShaderType('shadow', Shadow) + shManager.registerShaderType('roundedWithBorder', RoundedWithBorder) + shManager.registerShaderType('roundedWithShadow', RoundedWithShadow) + shManager.registerShaderType('roundedWithBorderAndShadow', RoundedWithBorderAndShadow) + shManager.registerShaderType('holePunch', HolePunch) + shManager.registerShaderType('roundedWithShadow', RadialGradient) + shManager.registerShaderType('roundedWithBorderAndShadow', LinearGradient) +} + +/** + * vite does not like dynamic links, this resolves that issue + */ +const shaderModules = { + webgl: () => import('@lightningjs/renderer/webgl/shaders'), + canvas: () => import('@lightningjs/renderer/canvas/shaders'), +} + +export default async () => { const stage = renderer.stage - Settings.get('shaders', []).forEach((shader) => { - stage.shManager.registerShaderType(shader.name, shader.type) - }) - Settings.get('effects', []).forEach((effect) => { - stage.shManager.registerShaderType(effect.name, effect.type) - }) + const renderMode = Settings.get('renderMode', 'webgl') + const { + Rounded, + Border, + Shadow, + RoundedWithBorder, + RoundedWithShadow, + RoundedWithBorderAndShadow, + HolePunch, + RadialGradient, + LinearGradient, + } = await shaderModules[renderMode]() + + registerBlitsDefaultShaders( + stage.shManager, + Rounded, + Border, + Shadow, + RoundedWithBorder, + RoundedWithShadow, + RoundedWithBorderAndShadow, + HolePunch, + RadialGradient, + LinearGradient + ) + + const shaders = Settings.get('shaders', []) + const len = shaders.length + for (let i = 0; i < len; i++) { + stage.shManager.registerShaderType(shaders[i].name, shaders[i].type) + } } diff --git a/src/launch.js b/src/launch.js index b894fb90..561452db 100644 --- a/src/launch.js +++ b/src/launch.js @@ -19,19 +19,35 @@ import Settings from './settings.js' import { initLog, Log } from './lib/log.js' import engine from './engine.js' import blitsPackageInfo from '../package.json' assert { type: 'json' } -import rendererPackageInfo from '../../renderer/package.json' assert { type: 'json' } export let renderer = {} export const stage = {} -export default (App, target, settings) => { +async function rendererVersion() { + let rendererPackageInfo + try { + // Dynamically import the renderer package.json + rendererPackageInfo = await import('../../renderer/package.json') + if (rendererPackageInfo !== undefined) { + return rendererPackageInfo.version + } + } catch (e) { + // Fallback to renderer version in dependencies + return blitsPackageInfo.dependencies['@lightningjs/renderer'] + } +} + +export default async (App, target, settings) => { Settings.set(settings) initLog() - Log.info('Blits Version ', blitsPackageInfo.version) - Log.info('Renderer Version ', rendererPackageInfo.version) + + rendererVersion().then((v) => { + Log.info('Blits Version ', blitsPackageInfo.version) + Log.info('Renderer Version ', v) + }) stage.element = engine.Element - renderer = engine.Launch(App, target, settings) + renderer = await engine.Launch(App, target, settings) } diff --git a/src/lib/colors/colors.js b/src/lib/colors/colors.js index bd7aed55..0be1ed9d 100644 --- a/src/lib/colors/colors.js +++ b/src/lib/colors/colors.js @@ -86,7 +86,6 @@ export default { console.warn('HSL(A) color format is not supported yet') return '0xffffffff' } - return defaultColor }, } diff --git a/src/lib/eventListeners.test.js b/src/lib/eventListeners.test.js index 57f9c887..a6327bbe 100644 --- a/src/lib/eventListeners.test.js +++ b/src/lib/eventListeners.test.js @@ -170,7 +170,11 @@ test('deregisterListener - only deregisters specified component listener', (t) = eventListener.deregisterListener(componentA, event) const result = eventListener.executeListeners(event) - t.equal(result, true, 'Should return true when listeners are still registered after deregistering one') + t.equal( + result, + true, + 'Should return true when listeners are still registered after deregistering one' + ) t.equal(callCount1, 0, 'Should not call deregistered callback') t.equal(callCount2, 1, 'Should call remaining registered callback') diff --git a/src/lib/shaders/shaders.js b/src/lib/shaders/shaders.js new file mode 100644 index 00000000..591781a6 --- /dev/null +++ b/src/lib/shaders/shaders.js @@ -0,0 +1,114 @@ +/* + * Copyright 2023 Comcast Cable Communications Management, LLC + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import { renderer } from '../../engines/L3/launch.js' +import { parseToObject, isObjectString, isArrayString } from '../utils.js' +import colors from '../colors/colors.js' + +export default { + createElementProps(v) { + let { border, shadow, rounded } = v + const props = {} + const hasRounded = rounded !== undefined + + if (border !== undefined && (typeof border === 'object' || isObjectString(border) === true)) { + border = this.parseProps(border) + const borderPrefix = hasRounded === true ? 'border-' : '' + for (const key in border) { + props[borderPrefix + key] = border[key] + } + } + if (shadow !== undefined && (typeof shadow === 'object' || isObjectString(shadow) === true)) { + shadow = this.parseProps(shadow) + const shadowPrefix = hasRounded === true ? 'shadow-' : '' + for (const key in border) { + props[shadowPrefix + key] = border[key] + } + } + + if (hasRounded === true && (typeof rounded === 'object' || isObjectString(rounded) === true)) { + if (typeof rounded === 'string') { + rounded = this.parseProps(rounded) + } + for (const key in rounded) { + props[key] = rounded[key] + } + } else if (hasRounded === true) { + if (isArrayString(rounded) === true) { + rounded = JSON.parse(rounded) + } + props['radius'] = rounded + } + return props + }, + createElementShader(v) { + let { border, shadow, rounded } = v + const props = this.createElementProps(v) + const hasShadow = shadow !== undefined + const hasBorder = border !== undefined + const hasRounded = rounded !== undefined + + if (hasBorder === true && hasShadow === true) { + return renderer.createShader('roundedWithBorderAndShadow', props) + } + + if (hasRounded === true) { + if (hasBorder === true) { + return renderer.createShader('roundedWithBorder', props) + } + + if (hasShadow === true) { + return renderer.createShader('roundedWithShadow', props) + } + + return renderer.createShader('rounded', props) + } + + if (hasRounded === false && hasBorder === true) { + return renderer.createShader('border', props) + } + + //only possible result left + return renderer.createShader('shadow', props) + }, + parseProp(v) { + if (typeof v === 'number') { + return v + } + if (typeof v === 'string') { + return colors.normalize(v) + } + if (Array.isArray(v) === true) { + return v.map((i) => this.parseProp(i)) + } + }, + parseProps(v) { + if (typeof v === 'string') { + v = parseToObject(v) + } + const keys = Object.keys(v) + const length = keys.length + for (let i = 0; i < length; i++) { + const key = keys[i] + if (key === 'type') { + continue + } + v[key] = this.parseProp(v[key]) + } + return v + }, +} diff --git a/src/lib/templateparser/parser.js b/src/lib/templateparser/parser.js index ded8c85f..380a2be4 100644 --- a/src/lib/templateparser/parser.js +++ b/src/lib/templateparser/parser.js @@ -189,7 +189,11 @@ export default (template = '', componentName, parentComponent, filePath = null) } // process color values - if (['color', ':color', ':effects', 'effects'].includes(name)) { + if ( + ['color', ':color', ':border', 'border', 'shadow', ':shadow', 'shader', ':shader'].includes( + name + ) + ) { return processColors(name, value) } return { name, value } diff --git a/src/lib/templateparser/parser.test.js b/src/lib/templateparser/parser.test.js index ad398530..1311f776 100644 --- a/src/lib/templateparser/parser.test.js +++ b/src/lib/templateparser/parser.test.js @@ -682,23 +682,12 @@ test('Parse template with attributes with values spread over multiple lines', (a const template = ` ` @@ -714,7 +703,7 @@ test('Parse template with attributes with values spread over multiple lines', (a x: '40', y: '40', color: '0xfb923cff', - ':effects': "[$shader( 'radius', {radius: 44} )]", + rounded: '44', }, { [componentType]: 'Element', @@ -722,7 +711,7 @@ test('Parse template with attributes with values spread over multiple lines', (a h: '120', x: '100', y: '100', - ':effects': "[ $shader( 'radius', { radius: 45 } ) ]", + rounded: '45', }, ], }, @@ -863,22 +852,12 @@ test('Parse template with attribute values with delimited either single or doubl ` @@ -894,7 +873,7 @@ test('Parse template with attribute values with delimited either single or doubl x: '40', y: '40', color: '0xfb923cff', - ':effects': '[$shader( "radius", {radius: 44} )]', + rounded: '44', }, { [componentType]: 'Element', @@ -902,7 +881,7 @@ test('Parse template with attribute values with delimited either single or doubl h: '120', x: '100', y: '100', - ':effects': "[ $shader( 'radius', { radius: 45 } ) ]", + rounded: '45', }, ], }, @@ -920,24 +899,14 @@ test('Parse template with multiple top level elements and parsing should fail', ` @@ -1257,9 +1226,10 @@ test('Parse template with color and effects attributes and parsing should conver - - - + + + + @@ -1295,18 +1265,24 @@ test('Parse template with color and effects attributes and parsing should conver }, { color: "{top: '0x44037aff'}", - ':effects': "[$shader('radius', {radius: $radius})]", + ':rounded': '$radius', [componentType]: 'Element', }, { ':color': '$colors.color2', - ':effects': "[$shader('radius', {radius: $radius / 2})]", + ':rounded': '$radius / 2', [componentType]: 'Element', }, { color: '0x00000000', - ':effects': - "[$shader('radius', {radius: 10}), $shader('border', {width: 20, color: '0x60a5faff'})]", + rounded: '10', + border: "{width: 20, color: '0x60a5faff'}", + [componentType]: 'Element', + }, + { + color: '0x0000ffff', + rounded: '10', + shadow: "{blur: 20, spread: 10, color: '0x60a5faff'}", [componentType]: 'Element', }, { diff --git a/src/lib/utils.js b/src/lib/utils.js index cf9e25d3..891cd3fd 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -10,3 +10,19 @@ export const screenResolutions = { '2160p': 2, 2160: 2, } + +export const isTransition = (value) => { + return value !== null && typeof value === 'object' && 'transition' in value === true +} + +export const isObjectString = (str) => { + return typeof str === 'string' && str.startsWith('{') && str.endsWith('}') +} + +export const isArrayString = (str) => { + return typeof str === 'string' && str.startsWith('[') && str.endsWith(']') +} + +export const parseToObject = (str) => { + return JSON.parse(str.replace(/'/g, '"').replace(/([\w-_]+)\s*:/g, '"$1":')) +} diff --git a/src/router/router.js b/src/router/router.js index ac4304c2..14ec15c7 100644 --- a/src/router/router.js +++ b/src/router/router.js @@ -23,15 +23,20 @@ import { Log } from '../lib/log.js' import { stage } from '../launch.js' import Focus from '../focus.js' import Announcer from '../announcer/announcer.js' +import Settings from '../settings.js' export let currentRoute -export const state = reactive({ - path: '', - navigating: false, - data: null, - params: null, - hash: '', -}) +export const state = reactive( + { + path: '', + navigating: false, + data: null, + params: null, + hash: '', + }, + Settings.get('reactivityMode'), + true +) // Changed from WeakMap to Map to allow for caching of views by the url hash. // We are manually doing the cleanup of the cache when the route is not marked as keepAlive. diff --git a/vscode/data/template-attributes.json b/vscode/data/template-attributes.json index 72c6eb9e..2072f6ae 100644 --- a/vscode/data/template-attributes.json +++ b/vscode/data/template-attributes.json @@ -231,6 +231,117 @@ "RouterView" ] }, + "border": { + "attrType": "regular", + "types": [ + "number", + { + "type": "object", + "properties": { + "width": { + "type": "number" + }, + "top": { + "type": "number" + }, + "right": { + "type": "number" + }, + "bottom": { + "type": "number" + }, + "left": { + "type": "number" + }, + "color": { + "type": "string" + } + } + } + ], + "defaultValue": "undefined", + "reactive": true, + "description": "Apply a border, default color is white.", + "usedIn": [ + "Element" + ] + }, + "rounded": { + "attrType": "regular", + "types": [ + "number", + { + "type": "object", + "properties": { + "radius": { + "type": "number" + }, + "top-left": { + "type": "number" + }, + "top-right": { + "type": "number" + }, + "bottom-right": { + "type": "number" + }, + "bottom-left": { + "type": "number" + } + } + } + ], + "defaultValue": "undefined", + "reactive": true, + "description": "Apply a to corners, or (a) specific corner(s)", + "usedIn": [ + "Element" + ] + }, + "shadow": { + "attrType": "regular", + "types": [ + { + "type": "object", + "properties": { + "x": { + "type": "number" + }, + "y": { + "type": "number" + }, + "blur": { + "type": "number" + }, + "spread": { + "type": "number" + }, + "color": { + "type": "string" + } + } + } + ], + "defaultValue": "undefined", + "reactive": true, + "description": "Apply a to corners, or (a) specific corner(s)", + "usedIn": [ + "Element" + ] + }, + "shader": { + "attrType": "regular", + "types": [ + "string", + "object" + ], + "defaultValue": null, + "reactive": true, + "description": "Enable a specific shader to apply.", + "usedIn": [ + "Element" + ] + }, "clipping": { "attrType": "regular", "types": [