mirror of
https://github.com/filamentphp/filament.git
synced 2026-10-24 12:04:06 +08:00
Merge branch '2.x' into extract-dropdown
This commit is contained in:
+1
-1
@@ -11,5 +11,5 @@ trim_trailing_whitespace = true
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.{yml, yaml}]
|
||||
[*.{yml,yaml}]
|
||||
indent_size = 2
|
||||
|
||||
@@ -2,91 +2,92 @@ name: Bug report
|
||||
description: Report a problem you're experiencing
|
||||
labels: bug,unconfirmed
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before opening a bug report, please search the existing issues.
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before opening a bug report, please search the existing issues.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information.
|
||||
- type: dropdown
|
||||
id: package
|
||||
attributes:
|
||||
label: Package
|
||||
description: Which package is this bug report for?
|
||||
options:
|
||||
- filament/filament
|
||||
- filament/forms
|
||||
- filament/tables
|
||||
- filament/spatie-laravel-media-library-plugin
|
||||
- filament/spatie-laravel-settings-plugin
|
||||
- filament/spatie-laravel-tags-plugin
|
||||
- filament/spatie-laravel-translatable-plugin
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: package-version
|
||||
attributes:
|
||||
label: Package Version
|
||||
description: Please provide the full version of the package you have installed.
|
||||
placeholder: v2.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: laravel-version
|
||||
attributes:
|
||||
label: Laravel Version
|
||||
description: Please provide the full Laravel version of your project.
|
||||
placeholder: v8.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: livewire-version
|
||||
attributes:
|
||||
label: Livewire Version
|
||||
description: Please provide the full Livewire version of your project, if applicable.
|
||||
placeholder: v2.0.0
|
||||
- type: input
|
||||
id: php-version
|
||||
attributes:
|
||||
label: PHP Version
|
||||
description: Please provide the full PHP version of your server.
|
||||
placeholder: PHP 8.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Problem description
|
||||
description: What happened when you experienced the problem?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expectation
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: What did you expect to happen instead?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Which steps do we need to take to reproduce the problem? Any code examples need to be **as short as possible**, remove any code that is unrelated to the bug. **This issue will be automatically closed and not reviewed if detailed replication steps are missing.**
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction repository
|
||||
description: The URL of a public Git repository which contains the minimal amount of code to reproduce the problem. This allows us to fix the problem much quicker. **This issue will be automatically closed and not reviewed if this is missing. Please make sure to format the URL starting with `https://github.com`.**
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: If applicable, provide relevant log output. No need for backticks here.
|
||||
render: shell
|
||||
Thank you for taking the time to file a bug report. To address this bug as fast as possible, we need some information.
|
||||
- type: dropdown
|
||||
id: package
|
||||
attributes:
|
||||
label: Package
|
||||
description: Which package is this bug report for?
|
||||
options:
|
||||
- filament/filament
|
||||
- filament/forms
|
||||
- filament/tables
|
||||
- filament/notifications
|
||||
- filament/spatie-laravel-media-library-plugin
|
||||
- filament/spatie-laravel-settings-plugin
|
||||
- filament/spatie-laravel-tags-plugin
|
||||
- filament/spatie-laravel-translatable-plugin
|
||||
- Other
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: package-version
|
||||
attributes:
|
||||
label: Package Version
|
||||
description: Please provide the full version of the package you have installed.
|
||||
placeholder: v2.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: laravel-version
|
||||
attributes:
|
||||
label: Laravel Version
|
||||
description: Please provide the full Laravel version of your project.
|
||||
placeholder: v8.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: livewire-version
|
||||
attributes:
|
||||
label: Livewire Version
|
||||
description: Please provide the full Livewire version of your project, if applicable.
|
||||
placeholder: v2.0.0
|
||||
- type: input
|
||||
id: php-version
|
||||
attributes:
|
||||
label: PHP Version
|
||||
description: Please provide the full PHP version of your server.
|
||||
placeholder: PHP 8.0.0
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Problem description
|
||||
description: What happened when you experienced the problem?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: expectation
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: What did you expect to happen instead?
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: steps
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Which steps do we need to take to reproduce the problem? Any code examples need to be **as short as possible**, remove any code that is unrelated to the bug. **This issue will be automatically closed and not reviewed if detailed replication steps are missing.**
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
id: reproduction
|
||||
attributes:
|
||||
label: Reproduction repository
|
||||
description: The URL of a public Git repository which contains the minimal amount of code to reproduce the problem. This allows us to fix the problem much quicker. **This issue will be automatically closed and not reviewed if this is missing. Please make sure to format the URL starting with `https://github.com`.**
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: If applicable, provide relevant log output. No need for backticks here.
|
||||
render: shell
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
name: fix-code-style
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
fix-code-style:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v2
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer install --no-interaction
|
||||
npm ci
|
||||
- name: Run Pint
|
||||
run: composer pint
|
||||
- name: Run Prettier
|
||||
run: npm run prettier
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: >
|
||||
chore: fix code style
|
||||
+147
-147
@@ -1,197 +1,197 @@
|
||||
name: manage-issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
check-repro:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
const URL_REGEXP = /### Reproduction repository[\r\n]+([^#]+)###/m
|
||||
const REPRO_STEPS_REGEXP = /### Steps to reproduce[\r\n]+([^#]+)###/m
|
||||
const LABEL_NEEDS_MORE_INFORMATION = 'needs more info'
|
||||
const LABEL_UNCONFIRMED = 'unconfirmed'
|
||||
check-repro:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@v3
|
||||
with:
|
||||
script: |
|
||||
const URL_REGEXP = /### Reproduction repository[\r\n]+([^#]+)###/m
|
||||
const REPRO_STEPS_REGEXP = /### Steps to reproduce[\r\n]+([^#]+)###/m
|
||||
const LABEL_NEEDS_MORE_INFORMATION = 'needs more info'
|
||||
const LABEL_UNCONFIRMED = 'unconfirmed'
|
||||
|
||||
function debug(...args) {
|
||||
core.info(args.map(JSON.stringify).join(' '))
|
||||
}
|
||||
function debug(...args) {
|
||||
core.info(args.map(JSON.stringify).join(' '))
|
||||
}
|
||||
|
||||
if (context.payload.comment) {
|
||||
debug('Ignoring comment update.')
|
||||
if (context.payload.comment) {
|
||||
debug('Ignoring comment update.')
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const user = context.payload.sender.login
|
||||
const issue = context.payload.issue
|
||||
const body = issue.body
|
||||
const urlMatch = body.match(URL_REGEXP)
|
||||
const reproStepsMatch = body.match(REPRO_STEPS_REGEXP)
|
||||
const url = urlMatch !== null ? urlMatch[1].trim() : null
|
||||
const reproSteps = reproStepsMatch !== null ? reproStepsMatch[1].trim() : null
|
||||
const user = context.payload.sender.login
|
||||
const issue = context.payload.issue
|
||||
const body = issue.body
|
||||
const urlMatch = body.match(URL_REGEXP)
|
||||
const reproStepsMatch = body.match(REPRO_STEPS_REGEXP)
|
||||
const url = urlMatch !== null ? urlMatch[1].trim() : null
|
||||
const reproSteps = reproStepsMatch !== null ? reproStepsMatch[1].trim() : null
|
||||
|
||||
debug(`Found URL '${url}'`)
|
||||
debug(`Found repro steps '${reproSteps}'`)
|
||||
debug(`Found URL '${url}'`)
|
||||
debug(`Found repro steps '${reproSteps}'`)
|
||||
|
||||
async function createComment(comment) {
|
||||
comment = comment
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.join('\n')
|
||||
.trim()
|
||||
async function createComment(comment) {
|
||||
comment = comment
|
||||
.split('\n')
|
||||
.map((line) => line.trim())
|
||||
.join('\n')
|
||||
.trim()
|
||||
|
||||
await github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment,
|
||||
})
|
||||
}
|
||||
await github.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: comment,
|
||||
})
|
||||
}
|
||||
|
||||
async function getGitHubActionComments() {
|
||||
debug(`Loading existing comments...`)
|
||||
async function getGitHubActionComments() {
|
||||
debug(`Loading existing comments...`)
|
||||
|
||||
const comments = await github.issues.listComments({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
const comments = await github.issues.listComments({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
|
||||
return comments.data.filter(comment => {
|
||||
debug(`comment by user: '${comment.user.login}'`)
|
||||
return comment.user.login === 'github-actions[bot]'
|
||||
})
|
||||
}
|
||||
return comments.data.filter(comment => {
|
||||
debug(`comment by user: '${comment.user.login}'`)
|
||||
return comment.user.login === 'github-actions[bot]'
|
||||
})
|
||||
}
|
||||
|
||||
async function getIssueLabels() {
|
||||
const issues = await github.issues.listLabelsOnIssue({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
async function getIssueLabels() {
|
||||
const issues = await github.issues.listLabelsOnIssue({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
})
|
||||
|
||||
return issues.data
|
||||
}
|
||||
return issues.data
|
||||
}
|
||||
|
||||
async function updateIssue(state, state_reason = null) {
|
||||
await github.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state,
|
||||
state_reason,
|
||||
})
|
||||
}
|
||||
async function updateIssue(state, state_reason = null) {
|
||||
await github.issues.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.issue.number,
|
||||
state,
|
||||
state_reason,
|
||||
})
|
||||
}
|
||||
|
||||
async function closeWithComment(comment) {
|
||||
if (issue.state !== 'open') {
|
||||
debug(`Issue is not open`)
|
||||
async function closeWithComment(comment) {
|
||||
if (issue.state !== 'open') {
|
||||
debug(`Issue is not open`)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const comments = await getGitHubActionComments()
|
||||
const comments = await getGitHubActionComments()
|
||||
|
||||
if (comments.length > 0) {
|
||||
debug(`Already commented on issue won't comment again`)
|
||||
if (comments.length > 0) {
|
||||
debug(`Already commented on issue won't comment again`)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
debug(`Missing required information`)
|
||||
debug(`Missing required information`)
|
||||
|
||||
await github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: [LABEL_NEEDS_MORE_INFORMATION],
|
||||
})
|
||||
await github.issues.addLabels({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
labels: [LABEL_NEEDS_MORE_INFORMATION],
|
||||
})
|
||||
|
||||
await createComment(comment)
|
||||
await createComment(comment)
|
||||
|
||||
await updateIssue('closed', 'not_planned')
|
||||
}
|
||||
await updateIssue('closed', 'not_planned')
|
||||
}
|
||||
|
||||
async function openWithComment(comment) {
|
||||
if (issue.state !== 'closed') {
|
||||
debug(`Issue is already open`)
|
||||
async function openWithComment(comment) {
|
||||
if (issue.state !== 'closed') {
|
||||
debug(`Issue is already open`)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const labels = await getIssueLabels()
|
||||
const label = labels.find(label => label.name === LABEL_NEEDS_MORE_INFORMATION)
|
||||
const labels = await getIssueLabels()
|
||||
const label = labels.find(label => label.name === LABEL_NEEDS_MORE_INFORMATION)
|
||||
|
||||
if (! label) {
|
||||
debug(`Issue was not tagged as needs information`)
|
||||
if (! label) {
|
||||
debug(`Issue was not tagged as needs information`)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const comments = await getGitHubActionComments()
|
||||
const comments = await getGitHubActionComments()
|
||||
|
||||
if (comments.length === 0) {
|
||||
debug(`Issue was closed by someone else, won't reopen`)
|
||||
if (comments.length === 0) {
|
||||
debug(`Issue was closed by someone else, won't reopen`)
|
||||
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
debug(`Reopening closed issue`)
|
||||
debug(`Reopening closed issue`)
|
||||
|
||||
await github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: LABEL_NEEDS_MORE_INFORMATION,
|
||||
})
|
||||
await github.issues.removeLabel({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
name: LABEL_NEEDS_MORE_INFORMATION,
|
||||
})
|
||||
|
||||
await createComment(comment)
|
||||
await createComment(comment)
|
||||
|
||||
await updateIssue('open')
|
||||
}
|
||||
await updateIssue('open')
|
||||
}
|
||||
|
||||
const COMMENT_HEADER = `
|
||||
Hey @${user}! We're sorry to hear that you've hit this issue. 💛
|
||||
`.trim()
|
||||
const COMMENT_HEADER = `
|
||||
Hey @${user}! We're sorry to hear that you've hit this issue. 💛
|
||||
`.trim()
|
||||
|
||||
const NO_REPRO_URL = (! url.includes('https://github.com/')) || url.includes('https://github.com/filamentphp/') || url === 'https://github.com/filamentphp'
|
||||
const NO_REPRO_STEPS = reproSteps.length < 25
|
||||
const NO_REPRO_URL = (! url.includes('https://github.com/')) || url.includes('https://github.com/filamentphp/') || url === 'https://github.com/filamentphp'
|
||||
const NO_REPRO_STEPS = reproSteps.length < 25
|
||||
|
||||
if (NO_REPRO_URL || NO_REPRO_STEPS) {
|
||||
let comment = `
|
||||
${COMMENT_HEADER}
|
||||
if (NO_REPRO_URL || NO_REPRO_STEPS) {
|
||||
let comment = `
|
||||
${COMMENT_HEADER}
|
||||
|
||||
`
|
||||
`
|
||||
|
||||
if (NO_REPRO_URL) {
|
||||
comment += `
|
||||
However, it looks like you forgot to fill in the reproduction repository URL. Can you edit your original post and then we'll look at your issue?
|
||||
if (NO_REPRO_URL) {
|
||||
comment += `
|
||||
However, it looks like you forgot to fill in the reproduction repository URL. Can you edit your original post and then we'll look at your issue?
|
||||
|
||||
We need a public Git repository which contains a Laravel app with the minimal amount of Filament code to reproduce the problem. That would allow us to download it and review your bug much easier, so it can be fixed quicker. Please make sure to include a database seeder with everything we need to set the app up quickly.
|
||||
`
|
||||
}
|
||||
We need a public Git repository which contains a Laravel app with the minimal amount of Filament code to reproduce the problem. That would allow us to download it and review your bug much easier, so it can be fixed quicker. Please make sure to include a database seeder with everything we need to set the app up quickly.
|
||||
`
|
||||
}
|
||||
|
||||
if (NO_REPRO_URL && NO_REPRO_STEPS) {
|
||||
comment += `
|
||||
if (NO_REPRO_URL && NO_REPRO_STEPS) {
|
||||
comment += `
|
||||
|
||||
Also, `
|
||||
} else if (NO_REPRO_STEPS) {
|
||||
comment += `
|
||||
Also, `
|
||||
} else if (NO_REPRO_STEPS) {
|
||||
comment += `
|
||||
|
||||
However, `
|
||||
}
|
||||
However, `
|
||||
}
|
||||
|
||||
if (NO_REPRO_STEPS) {
|
||||
comment += `it doesn't look like you've provided much information on how to replicate the issue. Please edit your original post with clear steps we need to take.`
|
||||
}
|
||||
if (NO_REPRO_STEPS) {
|
||||
comment += `it doesn't look like you've provided much information on how to replicate the issue. Please edit your original post with clear steps we need to take.`
|
||||
}
|
||||
|
||||
closeWithComment(comment)
|
||||
} else {
|
||||
openWithComment(`
|
||||
Thank you for providing reproduction steps! Reopening the issue now.
|
||||
`)
|
||||
}
|
||||
closeWithComment(comment)
|
||||
} else {
|
||||
openWithComment(`
|
||||
Thank you for providing reproduction steps! Reopening the issue now.
|
||||
`)
|
||||
}
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
name: monorepo-split
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: '*'
|
||||
push:
|
||||
tags: '*'
|
||||
|
||||
jobs:
|
||||
split-monorepo:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- admin
|
||||
- forms
|
||||
- notifications
|
||||
- spatie-laravel-media-library-plugin
|
||||
- spatie-laravel-settings-plugin
|
||||
- spatie-laravel-tags-plugin
|
||||
- spatie-laravel-translatable-plugin
|
||||
- support
|
||||
- tables
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- id: previous-tag
|
||||
uses: "WyriHaximus/github-action-get-previous-tag@master"
|
||||
- name: Monorepo Split of ${{ matrix.package }}
|
||||
uses: symplify/github-action-monorepo-split@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
with:
|
||||
package_directory: 'packages/${{ matrix.package }}'
|
||||
repository_organization: 'filamentphp'
|
||||
repository_name: '${{ matrix.package }}'
|
||||
branch: 2.x
|
||||
tag: ${{ steps.previous-tag.outputs.tag }}
|
||||
user_name: "Dan Harrin"
|
||||
user_email: "dan@danharrin.com"
|
||||
split-monorepo:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
package:
|
||||
- admin
|
||||
- forms
|
||||
- notifications
|
||||
- spatie-laravel-media-library-plugin
|
||||
- spatie-laravel-settings-plugin
|
||||
- spatie-laravel-tags-plugin
|
||||
- spatie-laravel-translatable-plugin
|
||||
- support
|
||||
- tables
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- id: previous-tag
|
||||
uses: 'WyriHaximus/github-action-get-previous-tag@master'
|
||||
- name: Monorepo Split of ${{ matrix.package }}
|
||||
uses: symplify/github-action-monorepo-split@main
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.ACCESS_TOKEN }}
|
||||
with:
|
||||
package_directory: 'packages/${{ matrix.package }}'
|
||||
repository_organization: 'filamentphp'
|
||||
repository_name: '${{ matrix.package }}'
|
||||
branch: 2.x
|
||||
tag: ${{ steps.previous-tag.outputs.tag }}
|
||||
user_name: 'Dan Harrin'
|
||||
user_email: 'dan@danharrin.com'
|
||||
|
||||
@@ -1,33 +1,33 @@
|
||||
name: npm-build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- 2.x
|
||||
types: [closed]
|
||||
pull_request:
|
||||
branches:
|
||||
- 2.x
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
npm-build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: 2.x
|
||||
npm-build:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.pull_request.merged == true
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: 2.x
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v2
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build assets
|
||||
run: npm run build
|
||||
- name: Build assets
|
||||
run: npm run build
|
||||
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
branch: 2.x
|
||||
commit_message: >
|
||||
chore: build assets
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
branch: 2.x
|
||||
commit_message: >
|
||||
chore: build assets
|
||||
|
||||
@@ -1,38 +1,38 @@
|
||||
name: phpstan
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [ 8.1, '8.0' ]
|
||||
laravel: [ 9.* ]
|
||||
dependency-version: [ prefer-stable ]
|
||||
include:
|
||||
- laravel: 9.*
|
||||
testbench: 7.*
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: mbstring, pdo, pdo_sqlite
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "filament/support" --no-interaction --no-update
|
||||
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
|
||||
- name: Run PHPStan
|
||||
run: ./vendor/bin/phpstan --error-format=github
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.1, '8.0']
|
||||
laravel: [9.*]
|
||||
dependency-version: [prefer-stable]
|
||||
include:
|
||||
- laravel: 9.*
|
||||
testbench: 7.*
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: mbstring, pdo, pdo_sqlite
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "filament/support" --no-interaction --no-update
|
||||
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
|
||||
- name: Run PHPStan
|
||||
run: ./vendor/bin/phpstan --error-format=github
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
name: pint
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
pint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
ref: ${{ github.head_ref }}
|
||||
- name: Install dependencies
|
||||
run: composer install --no-interaction
|
||||
- name: Run Pint
|
||||
run: ./vendor/bin/pint
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
commit_message: >
|
||||
chore: styling
|
||||
+35
-35
@@ -1,40 +1,40 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [ 8.1, '8.0' ]
|
||||
laravel: [ 9.*, 8.* ]
|
||||
dependency-version: [ prefer-stable ]
|
||||
include:
|
||||
- laravel: 9.*
|
||||
testbench: 7.*
|
||||
- laravel: 8.*
|
||||
testbench: 6.*
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: mbstring, pdo, pdo_sqlite
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "filament/support" --no-interaction --no-update
|
||||
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
|
||||
- name: Execute tests
|
||||
run: ./vendor/bin/pest --verbose
|
||||
run-tests:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php: [8.1, '8.0']
|
||||
laravel: [9.*, 8.*]
|
||||
dependency-version: [prefer-stable]
|
||||
include:
|
||||
- laravel: 9.*
|
||||
testbench: 7.*
|
||||
- laravel: 8.*
|
||||
testbench: 6.*
|
||||
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.composer/cache/files
|
||||
key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php }}
|
||||
extensions: mbstring, pdo, pdo_sqlite
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" "filament/support" --no-interaction --no-update
|
||||
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction
|
||||
- name: Execute tests
|
||||
run: ./vendor/bin/pest --verbose
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 4,
|
||||
"trailingComma": "all",
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
|
||||
@@ -76,11 +76,7 @@ Install the packages in your app's `composer.json`:
|
||||
{
|
||||
...
|
||||
"require": {
|
||||
"filament/filament": "dev-fix/error-message as 2.x-dev",
|
||||
"filament/forms": "dev-fix/error-message as 2.x-dev",
|
||||
"filament/notifications": "dev-fix/error-message as 2.x-dev",
|
||||
"filament/support": "dev-fix/error-message as 2.x-dev",
|
||||
"filament/tables": "dev-fix/error-message as 2.x-dev"
|
||||
"filament/filament": "*",
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"spatie/laravel-package-tools": "^1.9",
|
||||
"spatie/laravel-ray": "^1.29",
|
||||
"spatie/laravel-tags": "^4.2",
|
||||
"spatie/invade": "^1.0",
|
||||
"symplify/monorepo-builder": "^10.0",
|
||||
"tgalopin/html-sanitizer": "^1.5"
|
||||
},
|
||||
|
||||
Generated
+369
-399
File diff suppressed because it is too large
Load Diff
Generated
+174
-152
@@ -38,6 +38,7 @@
|
||||
"marked": "^4.0.10",
|
||||
"mdhl": "^0.0.6",
|
||||
"postcss": "^8.4.4",
|
||||
"prettier": "^2.7.1",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tailwindcss": "^3.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
@@ -98,30 +99,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/compat-data": {
|
||||
"version": "7.18.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz",
|
||||
"integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz",
|
||||
"integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz",
|
||||
"integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz",
|
||||
"integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.18.10",
|
||||
"@babel/generator": "^7.18.13",
|
||||
"@babel/helper-compilation-targets": "^7.18.9",
|
||||
"@babel/helper-module-transforms": "^7.18.9",
|
||||
"@babel/helpers": "^7.18.9",
|
||||
"@babel/parser": "^7.18.10",
|
||||
"@babel/parser": "^7.18.13",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.18.10",
|
||||
"@babel/types": "^7.18.10",
|
||||
"@babel/traverse": "^7.18.13",
|
||||
"@babel/types": "^7.18.13",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -146,12 +147,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.18.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz",
|
||||
"integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz",
|
||||
"integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.18.10",
|
||||
"@babel/types": "^7.18.13",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
@@ -226,9 +227,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz",
|
||||
"integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz",
|
||||
"integrity": "sha512-hDvXp+QYxSRL+23mpAlSGxHMDyIGChm0/AwTfTAAK5Ufe40nCsyNdaYCGuK91phn/fVu9kqayImRDkvNAgdrsA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-annotate-as-pure": "^7.18.6",
|
||||
@@ -601,9 +602,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.18.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz",
|
||||
"integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
|
||||
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@@ -1185,9 +1186,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-destructuring": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz",
|
||||
"integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz",
|
||||
"integrity": "sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-plugin-utils": "^7.18.9"
|
||||
@@ -1763,19 +1764,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.18.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz",
|
||||
"integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz",
|
||||
"integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.18.10",
|
||||
"@babel/generator": "^7.18.13",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.18.9",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.18.11",
|
||||
"@babel/types": "^7.18.10",
|
||||
"@babel/parser": "^7.18.13",
|
||||
"@babel/types": "^7.18.13",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@@ -1784,9 +1785,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
|
||||
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz",
|
||||
"integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.18.10",
|
||||
@@ -1896,9 +1897,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||
"version": "0.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
|
||||
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
@@ -1947,9 +1948,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@popperjs/core": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
|
||||
"version": "2.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
|
||||
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
|
||||
"dev": true,
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -2087,9 +2088,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/eslint": {
|
||||
"version": "8.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
|
||||
"integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==",
|
||||
"version": "8.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
|
||||
"integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/estree": "*",
|
||||
@@ -2219,9 +2220,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "18.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
|
||||
"integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
|
||||
"version": "18.7.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.10.tgz",
|
||||
"integrity": "sha512-SST7B//wF7xlGoLo1IEVB0cQ4e7BgXlDk5IaPgb5s0DlYLjb4if07h8+0zbQIvahfPNXs6e7zyT0EH1MqaS+5g==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
@@ -3217,9 +3218,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001375",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz",
|
||||
"integrity": "sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==",
|
||||
"version": "1.0.30001381",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001381.tgz",
|
||||
"integrity": "sha512-fEnkDOKpvp6qc+olg7+NzE1SqyfiyKf4uci7fAU38M3zxs0YOyKOxW/nMZ2l9sJbt7KZHcDIxUnbI0Iime7V4w==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@@ -3397,9 +3398,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/colord": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz",
|
||||
"integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==",
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
||||
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
@@ -3847,9 +3848,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cssnano": {
|
||||
"version": "5.1.12",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.12.tgz",
|
||||
"integrity": "sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ==",
|
||||
"version": "5.1.13",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.13.tgz",
|
||||
"integrity": "sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"cssnano-preset-default": "^5.2.12",
|
||||
@@ -3935,9 +3936,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/dayjs": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz",
|
||||
"integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz",
|
||||
"integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
@@ -4253,9 +4254,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.4.213",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.213.tgz",
|
||||
"integrity": "sha512-+3DbGHGOCHTVB/Ms63bGqbyC1b8y7Fk86+7ltssB8NQrZtSCvZG6eooSl9U2Q0yw++fL2DpHKOdTU0NVEkFObg==",
|
||||
"version": "1.4.225",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.225.tgz",
|
||||
"integrity": "sha512-ICHvGaCIQR3P88uK8aRtx8gmejbVJyC6bB4LEC3anzBrIzdzC7aiZHY4iFfXhN4st6I7lMO0x4sgBHf/7kBvRw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/elliptic": {
|
||||
@@ -6096,9 +6097,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz",
|
||||
"integrity": "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==",
|
||||
"version": "4.0.19",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.19.tgz",
|
||||
"integrity": "sha512-rgQF/OxOiLcvgUAj1Q1tAf4Bgxn5h5JZTp04Fx4XUkVhs7B+7YA9JEWJhJpoO8eJt8MkZMwqLCNeNqj1bCREZQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
@@ -6554,9 +6555,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"call-bind": "^1.0.2",
|
||||
@@ -7506,6 +7507,21 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"prettier": "bin-prettier.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pretty-time": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
|
||||
@@ -8410,9 +8426,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/std-env": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.1.1.tgz",
|
||||
"integrity": "sha512-/c645XdExBypL01TpFKiG/3RAa/Qmu+zRi0MwAmrdEkwHNuN0ebo8ccAXBBDa5Z0QOJgBskUIbuCK91x0sCVEw==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.2.1.tgz",
|
||||
"integrity": "sha512-D/uYFWkI/31OrnKmXZqGAGK5GbQRPp/BWA1nuITcc6ICblhhuQUPHS5E2GSCVS7Hwhf4ciq8qsATwBUxv+lI6w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/stream-browserify": {
|
||||
@@ -8687,16 +8703,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser-webpack-plugin": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz",
|
||||
"integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==",
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz",
|
||||
"integrity": "sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/trace-mapping": "^0.3.7",
|
||||
"@jridgewell/trace-mapping": "^0.3.14",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^3.1.1",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"terser": "^5.7.2"
|
||||
"terser": "^5.14.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.13.0"
|
||||
@@ -9257,9 +9273,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/webpack-dev-server": {
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz",
|
||||
"integrity": "sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==",
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz",
|
||||
"integrity": "sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/bonjour": "^3.5.9",
|
||||
@@ -9668,27 +9684,27 @@
|
||||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.18.8",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.8.tgz",
|
||||
"integrity": "sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.18.13.tgz",
|
||||
"integrity": "sha512-5yUzC5LqyTFp2HLmDoxGQelcdYgSpP9xsnMWBphAscOdFrHSAVbLNzWiy32sVNDqJRDiJK6klfDnAgu6PAGSHw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.10.tgz",
|
||||
"integrity": "sha512-JQM6k6ENcBFKVtWvLavlvi/mPcpYZ3+R+2EySDEMSMbp7Mn4FexlbbJVrx2R7Ijhr01T8gyqrOaABWIOgxeUyw==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz",
|
||||
"integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@ampproject/remapping": "^2.1.0",
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.18.10",
|
||||
"@babel/generator": "^7.18.13",
|
||||
"@babel/helper-compilation-targets": "^7.18.9",
|
||||
"@babel/helper-module-transforms": "^7.18.9",
|
||||
"@babel/helpers": "^7.18.9",
|
||||
"@babel/parser": "^7.18.10",
|
||||
"@babel/parser": "^7.18.13",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.18.10",
|
||||
"@babel/types": "^7.18.10",
|
||||
"@babel/traverse": "^7.18.13",
|
||||
"@babel/types": "^7.18.13",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
@@ -9705,12 +9721,12 @@
|
||||
}
|
||||
},
|
||||
"@babel/generator": {
|
||||
"version": "7.18.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.12.tgz",
|
||||
"integrity": "sha512-dfQ8ebCN98SvyL7IxNMCUtZQSq5R7kxgN+r8qYTGDmmSion1hX2C0zq2yo1bsCDhXixokv1SAWTZUMYbO/V5zg==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.13.tgz",
|
||||
"integrity": "sha512-CkPg8ySSPuHTYPJYo7IRALdqyjM9HCbt/3uOBEFbzyGVP6Mn8bwFPB0jX6982JVNBlYzM1nnPkfjuXSOPtQeEQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.18.10",
|
||||
"@babel/types": "^7.18.13",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"jsesc": "^2.5.1"
|
||||
},
|
||||
@@ -9768,9 +9784,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/helper-create-class-features-plugin": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.9.tgz",
|
||||
"integrity": "sha512-WvypNAYaVh23QcjpMR24CwZY2Nz6hqdOcFdPbNpV56hL5H6KiFheO7Xm1aPdlLQ7d5emYZX7VZwPp9x3z+2opw==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.13.tgz",
|
||||
"integrity": "sha512-hDvXp+QYxSRL+23mpAlSGxHMDyIGChm0/AwTfTAAK5Ufe40nCsyNdaYCGuK91phn/fVu9kqayImRDkvNAgdrsA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-annotate-as-pure": "^7.18.6",
|
||||
@@ -10054,9 +10070,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.18.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.11.tgz",
|
||||
"integrity": "sha512-9JKn5vN+hDt0Hdqn1PiJ2guflwP+B6Ga8qbDuoF0PzzVhrzsKIJo8yGqVk6CmMHiMei9w1C1Bp9IMJSIK+HPIQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.13.tgz",
|
||||
"integrity": "sha512-dgXcIfMuQ0kgzLB2b9tRZs7TTFFaGM2AbtA4fJgUUYukzGH4jwsS7hzQHEGs67jdehpm22vkgKwvbU+aEflgwg==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
|
||||
@@ -10437,9 +10453,9 @@
|
||||
}
|
||||
},
|
||||
"@babel/plugin-transform-destructuring": {
|
||||
"version": "7.18.9",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.9.tgz",
|
||||
"integrity": "sha512-p5VCYNddPLkZTq4XymQIaIfZNJwT9YsjkPOhkVEqt6QIpQFZVM9IltqqYpOEkJoN1DPznmxUDyZ5CTZs/ZCuHA==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz",
|
||||
"integrity": "sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-plugin-utils": "^7.18.9"
|
||||
@@ -10836,27 +10852,27 @@
|
||||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.18.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.11.tgz",
|
||||
"integrity": "sha512-TG9PiM2R/cWCAy6BPJKeHzNbu4lPzOSZpeMfeNErskGpTJx6trEvFaVCbDvpcxwy49BKWmEPwiW8mrysNiDvIQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.13.tgz",
|
||||
"integrity": "sha512-N6kt9X1jRMLPxxxPYWi7tgvJRH/rtoU+dbKAPDM44RFHiMH8igdsaSBgFeskhSl/kLWLDUvIh1RXCrTmg0/zvA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.18.6",
|
||||
"@babel/generator": "^7.18.10",
|
||||
"@babel/generator": "^7.18.13",
|
||||
"@babel/helper-environment-visitor": "^7.18.9",
|
||||
"@babel/helper-function-name": "^7.18.9",
|
||||
"@babel/helper-hoist-variables": "^7.18.6",
|
||||
"@babel/helper-split-export-declaration": "^7.18.6",
|
||||
"@babel/parser": "^7.18.11",
|
||||
"@babel/types": "^7.18.10",
|
||||
"@babel/parser": "^7.18.13",
|
||||
"@babel/types": "^7.18.13",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.18.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.10.tgz",
|
||||
"integrity": "sha512-MJvnbEiiNkpjo+LknnmRrqbY1GPUUggjv+wQVjetM/AONoupqRALB7I6jGqNUAZsKcRIEu2J6FRFvsczljjsaQ==",
|
||||
"version": "7.18.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.13.tgz",
|
||||
"integrity": "sha512-ePqfTihzW0W6XAU+aMw2ykilisStJfDnsejDCXRchCcMJ4O0+8DhPXf2YUbZ6wjBlsEmZwLK/sPweWtu8hcJYQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-string-parser": "^7.18.10",
|
||||
@@ -10947,9 +10963,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@jridgewell/trace-mapping": {
|
||||
"version": "0.3.14",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz",
|
||||
"integrity": "sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==",
|
||||
"version": "0.3.15",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz",
|
||||
"integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
@@ -10989,9 +11005,9 @@
|
||||
}
|
||||
},
|
||||
"@popperjs/core": {
|
||||
"version": "2.11.5",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz",
|
||||
"integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==",
|
||||
"version": "2.11.6",
|
||||
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz",
|
||||
"integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==",
|
||||
"dev": true
|
||||
},
|
||||
"@ryangjchandler/alpine-tooltip": {
|
||||
@@ -11116,9 +11132,9 @@
|
||||
}
|
||||
},
|
||||
"@types/eslint": {
|
||||
"version": "8.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz",
|
||||
"integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==",
|
||||
"version": "8.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz",
|
||||
"integrity": "sha512-/fqTbjxyFUaYNO7VcW5g+4npmqVACz1bB7RTHYuLj+PRjw9hrCwrUXVQFpChUS0JsyEFvMZ7U/PfmvWgxJhI9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/estree": "*",
|
||||
@@ -11248,9 +11264,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "18.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.6.5.tgz",
|
||||
"integrity": "sha512-Xjt5ZGUa5WusGZJ4WJPbOT8QOqp6nDynVFRKcUt32bOgvXEoc6o085WNkYTMO7ifAj2isEfQQ2cseE+wT6jsRw==",
|
||||
"version": "18.7.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.10.tgz",
|
||||
"integrity": "sha512-SST7B//wF7xlGoLo1IEVB0cQ4e7BgXlDk5IaPgb5s0DlYLjb4if07h8+0zbQIvahfPNXs6e7zyT0EH1MqaS+5g==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse-json": {
|
||||
@@ -12081,9 +12097,9 @@
|
||||
}
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001375",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001375.tgz",
|
||||
"integrity": "sha512-kWIMkNzLYxSvnjy0hL8w1NOaWNr2rn39RTAVyIwcw8juu60bZDWiF1/loOYANzjtJmy6qPgNmn38ro5Pygagdw==",
|
||||
"version": "1.0.30001381",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001381.tgz",
|
||||
"integrity": "sha512-fEnkDOKpvp6qc+olg7+NzE1SqyfiyKf4uci7fAU38M3zxs0YOyKOxW/nMZ2l9sJbt7KZHcDIxUnbI0Iime7V4w==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
@@ -12214,9 +12230,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"colord": {
|
||||
"version": "2.9.2",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz",
|
||||
"integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==",
|
||||
"version": "2.9.3",
|
||||
"resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
|
||||
"integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==",
|
||||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
@@ -12572,9 +12588,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"cssnano": {
|
||||
"version": "5.1.12",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.12.tgz",
|
||||
"integrity": "sha512-TgvArbEZu0lk/dvg2ja+B7kYoD7BBCmn3+k58xD0qjrGHsFzXY/wKTo9M5egcUCabPol05e/PVoIu79s2JN4WQ==",
|
||||
"version": "5.1.13",
|
||||
"resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.1.13.tgz",
|
||||
"integrity": "sha512-S2SL2ekdEz6w6a2epXn4CmMKU4K3KpcyXLKfAYc9UQQqJRkD/2eLUG0vJ3Db/9OvO5GuAdgXw3pFbR6abqghDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cssnano-preset-default": "^5.2.12",
|
||||
@@ -12636,9 +12652,9 @@
|
||||
}
|
||||
},
|
||||
"dayjs": {
|
||||
"version": "1.11.4",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.4.tgz",
|
||||
"integrity": "sha512-Zj/lPM5hOvQ1Bf7uAvewDaUcsJoI6JmNqmHhHl3nyumwe0XHwt8sWdOVAPACJzCebL8gQCi+K49w7iKWnGwX9g==",
|
||||
"version": "1.11.5",
|
||||
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz",
|
||||
"integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
@@ -12881,9 +12897,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.4.213",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.213.tgz",
|
||||
"integrity": "sha512-+3DbGHGOCHTVB/Ms63bGqbyC1b8y7Fk86+7ltssB8NQrZtSCvZG6eooSl9U2Q0yw++fL2DpHKOdTU0NVEkFObg==",
|
||||
"version": "1.4.225",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.225.tgz",
|
||||
"integrity": "sha512-ICHvGaCIQR3P88uK8aRtx8gmejbVJyC6bB4LEC3anzBrIzdzC7aiZHY4iFfXhN4st6I7lMO0x4sgBHf/7kBvRw==",
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
@@ -14268,9 +14284,9 @@
|
||||
}
|
||||
},
|
||||
"marked": {
|
||||
"version": "4.0.18",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.18.tgz",
|
||||
"integrity": "sha512-wbLDJ7Zh0sqA0Vdg6aqlbT+yPxqLblpAZh1mK2+AO2twQkPywvvqQNfEPVwSSRjZ7dZcdeVBIAgiO7MMp3Dszw==",
|
||||
"version": "4.0.19",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-4.0.19.tgz",
|
||||
"integrity": "sha512-rgQF/OxOiLcvgUAj1Q1tAf4Bgxn5h5JZTp04Fx4XUkVhs7B+7YA9JEWJhJpoO8eJt8MkZMwqLCNeNqj1bCREZQ==",
|
||||
"dev": true
|
||||
},
|
||||
"md5": {
|
||||
@@ -14620,9 +14636,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"object.assign": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.3.tgz",
|
||||
"integrity": "sha512-ZFJnX3zltyjcYJL0RoCJuzb+11zWGyaDbjgxZbdV7rFEcHQuYxrZqhow67aA7xpes6LhojyFDaBKAFfogQrikA==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
|
||||
"integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"call-bind": "^1.0.2",
|
||||
@@ -15239,6 +15255,12 @@
|
||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||
"dev": true
|
||||
},
|
||||
"prettier": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
||||
"dev": true
|
||||
},
|
||||
"pretty-time": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pretty-time/-/pretty-time-1.1.0.tgz",
|
||||
@@ -15969,9 +15991,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"std-env": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.1.1.tgz",
|
||||
"integrity": "sha512-/c645XdExBypL01TpFKiG/3RAa/Qmu+zRi0MwAmrdEkwHNuN0ebo8ccAXBBDa5Z0QOJgBskUIbuCK91x0sCVEw==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.2.1.tgz",
|
||||
"integrity": "sha512-D/uYFWkI/31OrnKmXZqGAGK5GbQRPp/BWA1nuITcc6ICblhhuQUPHS5E2GSCVS7Hwhf4ciq8qsATwBUxv+lI6w==",
|
||||
"dev": true
|
||||
},
|
||||
"stream-browserify": {
|
||||
@@ -16183,16 +16205,16 @@
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz",
|
||||
"integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==",
|
||||
"version": "5.3.5",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.5.tgz",
|
||||
"integrity": "sha512-AOEDLDxD2zylUGf/wxHxklEkOe2/r+seuyOWujejFrIxHf11brA1/dWQNIgXa1c6/Wkxgu7zvv0JhOWfc2ELEA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@jridgewell/trace-mapping": "^0.3.7",
|
||||
"@jridgewell/trace-mapping": "^0.3.14",
|
||||
"jest-worker": "^27.4.5",
|
||||
"schema-utils": "^3.1.1",
|
||||
"serialize-javascript": "^6.0.0",
|
||||
"terser": "^5.7.2"
|
||||
"terser": "^5.14.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"schema-utils": {
|
||||
@@ -16615,9 +16637,9 @@
|
||||
}
|
||||
},
|
||||
"webpack-dev-server": {
|
||||
"version": "4.9.3",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.9.3.tgz",
|
||||
"integrity": "sha512-3qp/eoboZG5/6QgiZ3llN8TUzkSpYg1Ko9khWX1h40MIEUNS2mDoIa8aXsPfskER+GbTvs/IJZ1QTBBhhuetSw==",
|
||||
"version": "4.10.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-4.10.0.tgz",
|
||||
"integrity": "sha512-7dezwAs+k6yXVFZ+MaL8VnE+APobiO3zvpp3rBHe/HmWQ+avwh0Q3d0xxacOiBybZZ3syTZw9HXzpa3YNbAZDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/bonjour": "^3.5.9",
|
||||
|
||||
+3
-1
@@ -2,7 +2,8 @@
|
||||
"name": "filament",
|
||||
"scripts": {
|
||||
"build": "node bin/build && mix --production",
|
||||
"watch": "node bin/build && mix watch"
|
||||
"watch": "node bin/build && mix watch",
|
||||
"prettier": "npx prettier --write '**/*.{css,js,yml}' '!**/dist' '!**/vendor'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@alpinejs/collapse": "^3.8.1",
|
||||
@@ -37,6 +38,7 @@
|
||||
"marked": "^4.0.10",
|
||||
"mdhl": "^0.0.6",
|
||||
"postcss": "^8.4.4",
|
||||
"prettier": "^2.7.1",
|
||||
"sortablejs": "^1.15.0",
|
||||
"tailwindcss": "^3.0.0",
|
||||
"tippy.js": "^6.3.7",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/vendor
|
||||
@@ -11,14 +11,38 @@ namespace Livewire\Testing {
|
||||
|
||||
public function assertHasNoFormErrors(array $keys = []): static {}
|
||||
|
||||
public function mountPageAction(string $name): static {}
|
||||
|
||||
public function setPageActionData(array $data): static {}
|
||||
|
||||
public function assertPageActionDataSet(array $data): static {}
|
||||
|
||||
public function callPageAction(string $name, array $data = [], array $arguments = []): static {}
|
||||
|
||||
public function callMountedPageAction(array $arguments = []): static {}
|
||||
|
||||
public function assertPageActionExists(string $name): static {}
|
||||
|
||||
public function assertPageActionVisible(string $name): static {}
|
||||
|
||||
public function assertPageActionHidden(string $name): static {}
|
||||
|
||||
public function assertPageActionEnabled(string $name): static {}
|
||||
|
||||
public function assertPageActionDisabled(string $name): static {}
|
||||
|
||||
public function assertPageActionHasIcon(string $name, string $icon): static {}
|
||||
|
||||
public function assertPageActionDoesNotHaveIcon(string $name, string $icon): static {}
|
||||
|
||||
public function assertPageActionHasLabel(string $name, string $label): static {}
|
||||
|
||||
public function assertPageActionHasColor(string $name, string $color): static {}
|
||||
|
||||
public function assertPageActionDoesNotHaveColor(string $name, string $color): static {}
|
||||
|
||||
public function assertPageActionDoesNotHaveLabel(string $name, string $label): static {}
|
||||
|
||||
public function assertPageActionHeld(string $name): static {}
|
||||
|
||||
public function assertHasPageActionErrors(array $keys = []): static {}
|
||||
|
||||
Generated
-4783
File diff suppressed because it is too large
Load Diff
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+53
-53
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+2
-2
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"/app.js": "/app.js?id=26af475756bce8ea9c5a2731dd4abc7a",
|
||||
"/app.css": "/app.css?id=ebeff99b287f00b7cdc421f9e1d60220"
|
||||
"/app.js": "/app.js?id=4fab5d479eb9dd235eb10ba839e10b36",
|
||||
"/app.css": "/app.css?id=0df761cd71b4d35d8c79d66ce261e46d"
|
||||
}
|
||||
|
||||
@@ -63,6 +63,14 @@ If you wish to translate the package, you may publish the language files using:
|
||||
php artisan vendor:publish --tag=filament-translations
|
||||
```
|
||||
|
||||
Since this package depends on other Filament packages, you may wish to translate those as well:
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag=filament-forms-translations
|
||||
php artisan vendor:publish --tag=filament-tables-translations
|
||||
php artisan vendor:publish --tag=filament-support-translations
|
||||
```
|
||||
|
||||
## Upgrading
|
||||
|
||||
To upgrade the package to the latest version, you must run:
|
||||
|
||||
@@ -58,6 +58,8 @@ When creating your resource, you may now use `--generate`:
|
||||
php artisan make:filament-resource Customer --generate
|
||||
```
|
||||
|
||||
> Note: If your table contains ENUM columns, `doctrine/dbal` is unable to scan your table and will crash. Hence Filament is unable to generate the schema for your resource if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808).
|
||||
|
||||
### Handling soft deletes
|
||||
|
||||
By default, you will not be able to interact with deleted records in the admin panel. If you'd like to add functionality to restore, force delete and filter trashed records in your resource, use the `--soft-deletes` flag when generating the resource:
|
||||
|
||||
@@ -403,6 +403,21 @@ public static function table(Table $table): Table
|
||||
}
|
||||
```
|
||||
|
||||
## Polling content
|
||||
|
||||
You may poll table content so that it refreshes at a set interval, using the `poll()` method:
|
||||
|
||||
```php
|
||||
use Filament\Resources\Table;
|
||||
|
||||
public static function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
// ...
|
||||
->poll('10s');
|
||||
}
|
||||
```
|
||||
|
||||
## Authorization
|
||||
|
||||
For authorization, Filament will observe any [model policies](https://laravel.com/docs/authorization#creating-policies) that are registered in your app.
|
||||
|
||||
@@ -64,6 +64,6 @@ Global search results will link to the [Edit page](editing-records) of your reso
|
||||
```php
|
||||
public static function getGlobalSearchResultUrl(Model $record): string
|
||||
{
|
||||
return route('users.edit', ['user' => $record]);
|
||||
return UserResource::getUrl('edit', ['record' => $record]);
|
||||
}
|
||||
```
|
||||
|
||||
@@ -138,6 +138,31 @@ Action::make('updateAuthor')
|
||||
])
|
||||
```
|
||||
|
||||
#### Filling default data
|
||||
|
||||
You may fill the form with default data, using the `mountUsing()` method:
|
||||
|
||||
```php
|
||||
use App\Models\User;
|
||||
use Filament\Forms;
|
||||
use Filament\Pages\Actions\Action;
|
||||
|
||||
Action::make('updateAuthor')
|
||||
->mountUsing(fn (Forms\ComponentContainer $form) => $form->fill([
|
||||
'authorId' => $this->record->author->id,
|
||||
]))
|
||||
->action(function (array $data): void {
|
||||
$this->record->author()->associate($data['authorId']);
|
||||
$this->record->save();
|
||||
})
|
||||
->form([
|
||||
Forms\Components\Select::make('authorId')
|
||||
->label('Author')
|
||||
->options(User::query()->pluck('name', 'id'))
|
||||
->required(),
|
||||
])
|
||||
```
|
||||
|
||||
### Setting a modal heading, subheading, and button label
|
||||
|
||||
You may customize the heading, subheading and button label of the modal:
|
||||
@@ -153,7 +178,7 @@ Action::make('delete')
|
||||
->modalButton('Yes, delete them')
|
||||
```
|
||||
|
||||
## Custom content
|
||||
### Custom content
|
||||
|
||||
You may define custom content to be rendered inside your modal, which you can specify by passing a Blade view into the `modalContent()` method:
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ use Filament\Navigation\NavigationGroup;
|
||||
|
||||
Filament::navigation(function (NavigationBuilder $builder): NavigationBuilder {
|
||||
return $builder
|
||||
->group([
|
||||
->groups([
|
||||
NavigationGroup::make('Website')
|
||||
->items([
|
||||
...PageResource::getNavigationItems(),
|
||||
|
||||
@@ -379,6 +379,31 @@ it('can validate invoice recipient email', function () {
|
||||
});
|
||||
```
|
||||
|
||||
To check if a page action is pre-filled with data, you can use the `assertPageActionDataSet()` method:
|
||||
|
||||
```php
|
||||
use function Pest\Livewire\livewire;
|
||||
|
||||
it('can send invoices to the primary contact by default', function () {
|
||||
$invoice = Invoice::factory()->create();
|
||||
$recipientEmail = $invoice->company->primaryContact->email;
|
||||
|
||||
livewire(EditInvoice::class, [
|
||||
'invoice' => $invoice,
|
||||
])
|
||||
->mountPageAction('send')
|
||||
->assertPageActionDataSet([
|
||||
'email' => $recipientEmail,
|
||||
])
|
||||
->callMountedPageAction()
|
||||
->assertHasNoPageActionErrors();
|
||||
|
||||
expect($invoice->refresh())
|
||||
->isSent()->toBeTrue()
|
||||
->recipient_email->toBe($recipientEmail);
|
||||
});
|
||||
```
|
||||
|
||||
To check if a page action is hidden to a user, you can use the `assertPageActionHidden()` method:
|
||||
|
||||
```php
|
||||
|
||||
@@ -40,7 +40,7 @@ Alpine.store('sidebar', {
|
||||
toggleCollapsedGroup: function (group) {
|
||||
this.collapsedGroups = this.collapsedGroups.includes(group)
|
||||
? this.collapsedGroups.filter(
|
||||
(collapsedGroup) => collapsedGroup !== group
|
||||
(collapsedGroup) => collapsedGroup !== group,
|
||||
)
|
||||
: this.collapsedGroups.concat(group)
|
||||
},
|
||||
@@ -56,7 +56,9 @@ Alpine.store('sidebar', {
|
||||
|
||||
Alpine.store(
|
||||
'theme',
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
|
||||
window.matchMedia('(prefers-color-scheme: dark)').matches
|
||||
? 'dark'
|
||||
: 'light',
|
||||
)
|
||||
|
||||
window.addEventListener('dark-mode-toggled', (event) => {
|
||||
|
||||
@@ -15,7 +15,7 @@ return [
|
||||
],
|
||||
|
||||
'save' => [
|
||||
'label' => 'Save',
|
||||
'label' => 'Save changes',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -19,7 +19,7 @@ return [
|
||||
],
|
||||
|
||||
'create_another' => [
|
||||
'label' => 'ساختن یکی دیگر',
|
||||
'label' => 'ساختن و ساختن یکی دیگر',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -2,16 +2,16 @@
|
||||
|
||||
return [
|
||||
|
||||
'title' => 'Tạo :label',
|
||||
'title' => 'Tạo :label mới',
|
||||
|
||||
'breadcrumb' => 'Tạo',
|
||||
'breadcrumb' => 'Tạo mới',
|
||||
|
||||
'form' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'cancel' => [
|
||||
'label' => 'Hủy',
|
||||
'label' => 'Quay lại',
|
||||
],
|
||||
|
||||
'create' => [
|
||||
|
||||
@@ -2,20 +2,20 @@
|
||||
|
||||
return [
|
||||
|
||||
'title' => 'Sửa :label',
|
||||
'title' => 'Chỉnh sửa :label',
|
||||
|
||||
'breadcrumb' => 'Sửa',
|
||||
'breadcrumb' => 'Chỉnh sửa',
|
||||
|
||||
'form' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'cancel' => [
|
||||
'label' => 'Hủy',
|
||||
'label' => 'Quay lại',
|
||||
],
|
||||
|
||||
'save' => [
|
||||
'label' => 'Lưu',
|
||||
'label' => 'Lưu lại',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
@@ -18,31 +18,22 @@
|
||||
@if (config('filament.layout.sidebar.is_collapsible_on_desktop'))
|
||||
<div
|
||||
x-data="{}"
|
||||
class="flex-col flex-1 hidden w-screen h-full space-y-6 transition-all filament-main lg:pl-[var(--sidebar-width)] rtl:lg:pl-0 rtl:lg:pr-[var(--sidebar-width)]"
|
||||
class="flex-col flex-1 hidden w-screen h-full gap-y-6 transition-all filament-main rtl:lg:pl-0"
|
||||
x-bind:class="{
|
||||
'lg:pl-[5.4rem] rtl:lg:pr-[5.4rem]': ! $store.sidebar.isOpen
|
||||
'lg:pl-[5.4rem] rtl:lg:pr-[5.4rem]': ! $store.sidebar.isOpen,
|
||||
'lg:pl-[var(--sidebar-width)] rtl:lg:pr-[var(--sidebar-width)]': $store.sidebar.isOpen,
|
||||
}"
|
||||
x-bind:style="'display: flex'" {{-- Mimics `x-cloak`, as using `x-cloak` causes visual issues with chart widgets --}}
|
||||
>
|
||||
@else
|
||||
<div class="flex flex-col flex-1 w-screen space-y-6 lg:pl-[var(--sidebar-width)] rtl:lg:pl-0 rtl:lg:pr-[var(--sidebar-width)] filament-main">
|
||||
<div class="flex flex-col flex-1 w-screen gap-y-6 lg:pl-[var(--sidebar-width)] rtl:lg:pl-0 rtl:lg:pr-[var(--sidebar-width)] filament-main">
|
||||
@endif
|
||||
<header @class([
|
||||
'h-[4rem] shrink-0 w-full border-b flex items-center filament-main-topbar',
|
||||
'dark:bg-gray-800 dark:border-gray-700' => config('filament.dark_mode'),
|
||||
])>
|
||||
<div @class([
|
||||
'flex items-center w-full px-2 mx-auto sm:px-4 md:px-6 lg:px-8',
|
||||
match ($maxContentWidth ?? config('filament.layout.max_content_width')) {
|
||||
'xl' => 'max-w-xl',
|
||||
'2xl' => 'max-w-2xl',
|
||||
'3xl' => 'max-w-3xl',
|
||||
'4xl' => 'max-w-4xl',
|
||||
'5xl' => 'max-w-5xl',
|
||||
'6xl' => 'max-w-6xl',
|
||||
'full' => 'max-w-full',
|
||||
default => 'max-w-7xl',
|
||||
},
|
||||
'flex items-center w-full px-2 sm:px-4 md:px-6 lg:px-8',
|
||||
])>
|
||||
<button
|
||||
x-data="{}"
|
||||
@@ -68,7 +59,8 @@
|
||||
|
||||
<div @class([
|
||||
'flex-1 w-full px-4 mx-auto md:px-6 lg:px-8 filament-main-content',
|
||||
match ($maxContentWidth ?? config('filament.layout.max_content_width')) {
|
||||
match ($maxContentWidth ??= config('filament.layout.max_content_width')) {
|
||||
null, '7xl', '' => 'max-w-7xl',
|
||||
'xl' => 'max-w-xl',
|
||||
'2xl' => 'max-w-2xl',
|
||||
'3xl' => 'max-w-3xl',
|
||||
@@ -76,7 +68,7 @@
|
||||
'5xl' => 'max-w-5xl',
|
||||
'6xl' => 'max-w-6xl',
|
||||
'full' => 'max-w-full',
|
||||
default => 'max-w-7xl',
|
||||
default => $maxContentWidth,
|
||||
},
|
||||
])>
|
||||
{{ \Filament\Facades\Filament::renderHook('content.start') }}
|
||||
|
||||
@@ -69,11 +69,11 @@
|
||||
|
||||
<div>
|
||||
@if (config('filament.dark_mode'))
|
||||
<x-filament::dropdown.item icon="heroicon-s-moon" x-show="theme === 'dark'" x-on:click="mode = 'manual'; theme = 'light'">
|
||||
<x-filament::dropdown.item icon="heroicon-s-moon" x-show="theme === 'dark'" x-on:click="close(); mode = 'manual'; theme = 'light'">
|
||||
{{ __('filament::layout.buttons.light_mode.label') }}
|
||||
</x-filament::dropdown.item>
|
||||
|
||||
<x-filament::dropdown.item icon="heroicon-s-sun" x-show="theme === 'light'" x-on:click="mode = 'manual'; theme = 'dark'">
|
||||
<x-filament::dropdown.item icon="heroicon-s-sun" x-show="theme === 'light'" x-on:click="close(); mode = 'manual'; theme = 'dark'">
|
||||
{{ __('filament::layout.buttons.dark_mode.label') }}
|
||||
</x-filament::dropdown.item>
|
||||
@endif
|
||||
|
||||
@@ -35,7 +35,22 @@
|
||||
$action = $this->getMountedAction();
|
||||
@endphp
|
||||
|
||||
<x-filament::modal id="page-action" :visible="filled($action)" :width="$action?->getModalWidth()" display-classes="block">
|
||||
<x-filament::modal
|
||||
id="page-action"
|
||||
:wire:key="$action ? $this->id . '.actions.' . $action->getName() . '.modal' : null"
|
||||
x-init="
|
||||
$watch('isOpen', () => {
|
||||
if (isOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
$wire.mountedAction = null
|
||||
})
|
||||
"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
display-classes="block"
|
||||
>
|
||||
@if ($action)
|
||||
@if ($action->isModalCentered())
|
||||
<x-slot name="heading">
|
||||
@@ -52,6 +67,12 @@
|
||||
<x-filament::modal.heading>
|
||||
{{ $action->getModalHeading() }}
|
||||
</x-filament::modal.heading>
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-filament::modal.subheading>
|
||||
{{ $subheading }}
|
||||
</x-filament::modal.subheading>
|
||||
@endif
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
|
||||
@@ -15,7 +15,10 @@
|
||||
<x-filament::button
|
||||
:form="$getForm()"
|
||||
:type="$canSubmitForm() ? 'submit' : 'button'"
|
||||
:tag="$action->getUrl() ? 'a' : 'button'"
|
||||
:wire:click="$wireClickAction"
|
||||
:href="$action->isEnabled() ? $action->getUrl() : null"
|
||||
:target="$action->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
:x-on:click="$canCancelAction() ? 'isOpen = false' : null"
|
||||
:color="$getColor()"
|
||||
:outlined="$isOutlined()"
|
||||
|
||||
@@ -18,6 +18,7 @@ use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\Action as TableAction;
|
||||
use Filament\Tables\Actions\ButtonAction;
|
||||
use Filament\Tables\Actions\IconButtonAction;
|
||||
use Filament\Testing\TestsPageActions;
|
||||
use Filament\Testing\TestsPages;
|
||||
use Filament\Widgets\Widget;
|
||||
use Illuminate\Filesystem\Filesystem;
|
||||
@@ -79,7 +80,9 @@ class FilamentServiceProvider extends PluginServiceProvider
|
||||
{
|
||||
parent::packageRegistered();
|
||||
|
||||
$this->registerComponents();
|
||||
$this->app->booting(function () {
|
||||
$this->registerComponents();
|
||||
});
|
||||
|
||||
$this->app->scoped('filament', function (): FilamentManager {
|
||||
return new FilamentManager();
|
||||
@@ -99,6 +102,7 @@ class FilamentServiceProvider extends PluginServiceProvider
|
||||
|
||||
$this->bootTableActionConfiguration();
|
||||
|
||||
TestableLivewire::mixin(new TestsPageActions());
|
||||
TestableLivewire::mixin(new TestsPages());
|
||||
}
|
||||
|
||||
|
||||
@@ -66,6 +66,8 @@ class CreateAction extends Action
|
||||
}
|
||||
|
||||
$this->success();
|
||||
|
||||
$this->record(null);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -62,8 +62,6 @@ trait HasActions
|
||||
} finally {
|
||||
$this->mountedAction = null;
|
||||
|
||||
$action->record(null);
|
||||
|
||||
$action->resetArguments();
|
||||
$action->resetFormData();
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ class EditRecord extends Page implements HasFormActions
|
||||
{
|
||||
$this->callHook('beforeFill');
|
||||
|
||||
$data = $this->getRecord()->toArray();
|
||||
$data = $this->getRecord()->attributesToArray();
|
||||
|
||||
$data = $this->mutateFormDataBeforeFill($data);
|
||||
|
||||
|
||||
@@ -384,17 +384,61 @@ class ListRecords extends Page implements Tables\Contracts\HasTable
|
||||
return filled($this->getTableReorderColumn()) && static::getResource()::canReorder();
|
||||
}
|
||||
|
||||
protected function getTablePollingInterval(): ?string
|
||||
{
|
||||
return $this->getResourceTable()->getPollingInterval();
|
||||
}
|
||||
|
||||
protected function getTableRecordUrlUsing(): ?Closure
|
||||
{
|
||||
return function (Model $record): ?string {
|
||||
$resource = static::getResource();
|
||||
foreach (['view', 'edit'] as $action) {
|
||||
$action = $this->getCachedTableAction($action);
|
||||
|
||||
if ($resource::hasPage('view') && $resource::canView($record)) {
|
||||
return $resource::getUrl('view', ['record' => $record]);
|
||||
if (! $action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$action->record($record);
|
||||
|
||||
if ($action->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $action->getUrl();
|
||||
|
||||
if (! $url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ($resource::hasPage('edit') && $resource::canEdit($record)) {
|
||||
return $resource::getUrl('edit', ['record' => $record]);
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
protected function getTableRecordActionUsing(): ?Closure
|
||||
{
|
||||
return function (Model $record): ?string {
|
||||
foreach (['view', 'edit'] as $action) {
|
||||
$action = $this->getCachedTableAction($action);
|
||||
|
||||
if (! $action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$action->record($record);
|
||||
|
||||
if ($action->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($action->getUrl()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $action->getName();
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
@@ -37,7 +37,7 @@ trait CanEditRecords
|
||||
$this->callHook('beforeFill');
|
||||
$this->callHook('beforeEditFill');
|
||||
|
||||
$data = $this->getMountedTableActionRecord()->toArray();
|
||||
$data = $this->getMountedTableActionRecord()->attributesToArray();
|
||||
|
||||
$data = $this->mutateFormDataBeforeFill($data);
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ trait CanViewRecords
|
||||
$this->callHook('beforeFill');
|
||||
$this->callHook('beforeViewFill');
|
||||
|
||||
$data = $this->getMountedTableActionRecord()->toArray();
|
||||
$data = $this->getMountedTableActionRecord()->attributesToArray();
|
||||
|
||||
$this->getMountedTableActionForm()->fill($data);
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class ViewRecord extends Page
|
||||
{
|
||||
$this->callHook('beforeFill');
|
||||
|
||||
$data = $this->getRecord()->toArray();
|
||||
$data = $this->getRecord()->attributesToArray();
|
||||
|
||||
$data = $this->mutateFormDataBeforeFill($data);
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ trait CanEditRecords
|
||||
$this->callHook('beforeFill');
|
||||
$this->callHook('beforeEditFill');
|
||||
|
||||
$data = $this->getMountedTableActionRecord()->toArray();
|
||||
$data = $this->getMountedTableActionRecord()->attributesToArray();
|
||||
|
||||
$data = $this->mutateFormDataBeforeFill($data);
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ trait CanViewRecords
|
||||
$this->callHook('beforeFill');
|
||||
$this->callHook('beforeViewFill');
|
||||
|
||||
$data = $this->getMountedTableActionRecord()->toArray();
|
||||
$data = $this->getMountedTableActionRecord()->attributesToArray();
|
||||
|
||||
$this->getMountedTableActionForm()->fill($data);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Filament\Resources\RelationManagers;
|
||||
|
||||
use Closure;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Http\Livewire\Concerns\CanNotify;
|
||||
use function Filament\locale_has_pluralization;
|
||||
@@ -418,6 +419,11 @@ class RelationManager extends Component implements Tables\Contracts\HasRelations
|
||||
return filled($this->getTableReorderColumn()) && $this->canReorder();
|
||||
}
|
||||
|
||||
protected function getTablePollingInterval(): ?string
|
||||
{
|
||||
return $this->getResourceTable()->getPollingInterval();
|
||||
}
|
||||
|
||||
protected function getTableHeading(): ?string
|
||||
{
|
||||
return static::getTitle();
|
||||
@@ -517,4 +523,60 @@ class RelationManager extends Component implements Tables\Contracts\HasRelations
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
protected function getTableRecordUrlUsing(): ?Closure
|
||||
{
|
||||
return function (Model $record): ?string {
|
||||
foreach (['view', 'edit'] as $action) {
|
||||
$action = $this->getCachedTableAction($action);
|
||||
|
||||
if (! $action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$action->record($record);
|
||||
|
||||
if ($action->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$url = $action->getUrl();
|
||||
|
||||
if (! $url) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
protected function getTableRecordActionUsing(): ?Closure
|
||||
{
|
||||
return function (Model $record): ?string {
|
||||
foreach (['view', 'edit'] as $action) {
|
||||
$action = $this->getCachedTableAction($action);
|
||||
|
||||
if (! $action) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$action->record($record);
|
||||
|
||||
if ($action->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($action->getUrl()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $action->getName();
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ class Table
|
||||
|
||||
protected array $headerActions = [];
|
||||
|
||||
protected ?string $pollingInterval = null;
|
||||
|
||||
protected ?string $reorderColumn = null;
|
||||
|
||||
final public function __construct()
|
||||
@@ -127,6 +129,13 @@ class Table
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function poll(?string $interval = '10s'): static
|
||||
{
|
||||
$this->pollingInterval = $interval;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use `appendActions()` instead.
|
||||
*/
|
||||
@@ -208,4 +217,9 @@ class Table
|
||||
{
|
||||
return $this->reorderColumn;
|
||||
}
|
||||
|
||||
public function getPollingInterval(): ?string
|
||||
{
|
||||
return $this->pollingInterval;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Testing;
|
||||
|
||||
use Closure;
|
||||
use Filament\Pages\Actions\Action;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Testing\TestsActions;
|
||||
use Illuminate\Testing\Assert;
|
||||
use Livewire\Testing\TestableLivewire;
|
||||
|
||||
/**
|
||||
* @method Page instance()
|
||||
*
|
||||
* @mixin TestableLivewire
|
||||
* @mixin TestsActions
|
||||
*/
|
||||
class TestsPageActions
|
||||
{
|
||||
public function mountPageAction(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
$name = $this->parseActionName($name);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionVisible($name);
|
||||
|
||||
$this->call('mountAction', $name);
|
||||
|
||||
$action = $this->instance()->getCachedAction($name);
|
||||
|
||||
if (! $action->shouldOpenModal()) {
|
||||
$this->assertNotDispatchedBrowserEvent('open-modal');
|
||||
|
||||
$this->assertNotSet('mountedAction', $action->getName());
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->assertSet('mountedAction', $name);
|
||||
|
||||
$this->assertDispatchedBrowserEvent('open-modal', [
|
||||
'id' => 'page-action',
|
||||
]);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function setPageActionData(): Closure
|
||||
{
|
||||
return function (array $data): static {
|
||||
$this->set('mountedActionData', $data);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionDataSet(): Closure
|
||||
{
|
||||
return function (array $data): static {
|
||||
foreach ($data as $key => $value) {
|
||||
$this->assertSet("mountedActionData.{$key}", $value);
|
||||
}
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function callPageAction(): Closure
|
||||
{
|
||||
return function (string $name, array $data = [], array $arguments = []): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->mountPageAction($name);
|
||||
|
||||
if (! $this->instance()->getMountedAction()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->setPageActionData($data);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->callMountedPageAction($arguments);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function callMountedPageAction(): Closure
|
||||
{
|
||||
return function (array $arguments = []): static {
|
||||
$action = $this->instance()->getMountedAction();
|
||||
|
||||
if (! $action) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->call('callMountedAction', json_encode($arguments));
|
||||
|
||||
if ($this->get('mountedAction') !== $action->getName()) {
|
||||
$this->assertDispatchedBrowserEvent('close-modal', [
|
||||
'id' => 'page-action',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionExists(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertInstanceOf(
|
||||
Action::class,
|
||||
$action,
|
||||
message: "Failed asserting that an action with name [{$name}] exists on the [{$livewireClass}] page.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionVisible(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertFalse(
|
||||
$action->isHidden(),
|
||||
message: "Failed asserting that an action with name [{$name}] is visible on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionHidden(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertTrue(
|
||||
$action->isHidden(),
|
||||
message: "Failed asserting that an action with name [{$name}] is hidden on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionEnabled(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertTrue(
|
||||
$action->isEnabled(),
|
||||
message: "Failed asserting that an action with name [{$name}] is enabled on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionDisabled(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertTrue(
|
||||
$action->isDisabled(),
|
||||
message: "Failed asserting that an action with name [{$name}] is disabled on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionHasIcon(): Closure
|
||||
{
|
||||
return function (string $name, string $icon, $record = null): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertTrue(
|
||||
$action->getIcon() === $icon,
|
||||
message: "Failed asserting that an action with name [{$name}] has icon [{$icon}] on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionDoesNotHaveIcon(): Closure
|
||||
{
|
||||
return function (string $name, string $icon, $record = null): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertFalse(
|
||||
$action->getIcon() === $icon,
|
||||
message: "Failed asserting that an action with name [{$name}] does not have icon [{$icon}] on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionHasLabel(): Closure
|
||||
{
|
||||
return function (string $name, string $label, $record = null): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertTrue(
|
||||
$action->getLabel() === $label,
|
||||
message: "Failed asserting that an action with name [{$name}] has label [{$label}] on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionDoesNotHaveLabel(): Closure
|
||||
{
|
||||
return function (string $name, string $label, $record = null): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertFalse(
|
||||
$action->getLabel() === $label,
|
||||
message: "Failed asserting that an action with name [{$name}] does not have label [{$label}] on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionHasColor(): Closure
|
||||
{
|
||||
return function (string $name, string $color, $record = null): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertTrue(
|
||||
$action->getColor() === $color,
|
||||
message: "Failed asserting that an action with name [{$name}] has color [{$color}] on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionDoesNotHaveColor(): Closure
|
||||
{
|
||||
return function (string $name, string $color, $record = null): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertFalse(
|
||||
$action->getColor() === $color,
|
||||
message: "Failed asserting that an action with name [{$name}] does not have color [{$color}] on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionHeld(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
$name = $this->parseActionName($name);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$this->assertSet('mountedAction', $name);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertHasPageActionErrors(): Closure
|
||||
{
|
||||
return function (array $keys = []): static {
|
||||
$this->assertHasErrors(
|
||||
collect($keys)
|
||||
->mapWithKeys(function ($value, $key): array {
|
||||
if (is_int($key)) {
|
||||
return [$key => "mountedActionData.{$value}"];
|
||||
}
|
||||
|
||||
return ["mountedActionData.{$key}" => $value];
|
||||
})
|
||||
->all(),
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertHasNoPageActionErrors(): Closure
|
||||
{
|
||||
return function (array $keys = []): static {
|
||||
$this->assertHasNoErrors(
|
||||
collect($keys)
|
||||
->mapWithKeys(function ($value, $key): array {
|
||||
if (is_int($key)) {
|
||||
return [$key => "mountedActionData.{$value}"];
|
||||
}
|
||||
|
||||
return ["mountedActionData.{$key}" => $value];
|
||||
})
|
||||
->all(),
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -3,10 +3,8 @@
|
||||
namespace Filament\Testing;
|
||||
|
||||
use Closure;
|
||||
use Filament\Pages\Actions\Action;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Testing\TestsActions;
|
||||
use Illuminate\Testing\Assert;
|
||||
use Livewire\Testing\TestableLivewire;
|
||||
|
||||
/**
|
||||
@@ -74,154 +72,4 @@ class TestsPages
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function callPageAction(): Closure
|
||||
{
|
||||
return function (string $name, array $data = [], array $arguments = []): static {
|
||||
$name = $this->parseActionName($name);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionVisible($name);
|
||||
|
||||
$this->call('mountAction', $name);
|
||||
|
||||
$action = $this->instance()->getCachedAction($name);
|
||||
|
||||
if (! $action->shouldOpenModal()) {
|
||||
$this->assertNotDispatchedBrowserEvent('open-modal');
|
||||
|
||||
$this->assertNotSet('mountedAction', $name);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->assertSet('mountedAction', $name);
|
||||
|
||||
$this->assertDispatchedBrowserEvent('open-modal', [
|
||||
'id' => 'page-action',
|
||||
]);
|
||||
|
||||
$this->set('mountedActionData', $data);
|
||||
|
||||
$this->call('callMountedAction', json_encode($arguments));
|
||||
|
||||
if ($this->get('mountedAction') !== $name) {
|
||||
$this->assertDispatchedBrowserEvent('close-modal', [
|
||||
'id' => 'page-action',
|
||||
]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionExists(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertInstanceOf(
|
||||
Action::class,
|
||||
$action,
|
||||
message: "Failed asserting that an action with name [{$name}] exists on the [{$livewireClass}] page.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionVisible(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertFalse(
|
||||
$action->isHidden(),
|
||||
message: "Failed asserting that an action with name [{$name}] is visible on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionHidden(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$livewire = $this->instance();
|
||||
$livewireClass = $livewire::class;
|
||||
|
||||
$action = $livewire->getCachedAction($name);
|
||||
|
||||
Assert::assertTrue(
|
||||
$action->isHidden(),
|
||||
message: "Failed asserting that an action with name [{$name}] is hidden on the [{$livewireClass}] component.",
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertPageActionHeld(): Closure
|
||||
{
|
||||
return function (string $name): static {
|
||||
$name = $this->parseActionName($name);
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$this->assertPageActionExists($name);
|
||||
|
||||
$this->assertSet('mountedAction', $name);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertHasPageActionErrors(): Closure
|
||||
{
|
||||
return function (array $keys = []): static {
|
||||
$this->assertHasErrors(
|
||||
collect($keys)
|
||||
->mapWithKeys(function ($value, $key): array {
|
||||
if (is_int($key)) {
|
||||
return [$key => "mountedActionData.{$value}"];
|
||||
}
|
||||
|
||||
return ["mountedActionData.{$key}" => $value];
|
||||
})
|
||||
->all(),
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
|
||||
public function assertHasNoPageActionErrors(): Closure
|
||||
{
|
||||
return function (array $keys = []): static {
|
||||
$this->assertHasNoErrors(
|
||||
collect($keys)
|
||||
->mapWithKeys(function ($value, $key): array {
|
||||
if (is_int($key)) {
|
||||
return [$key => "mountedActionData.{$value}"];
|
||||
}
|
||||
|
||||
return ["mountedActionData.{$key}" => $value];
|
||||
})
|
||||
->all(),
|
||||
);
|
||||
|
||||
return $this;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
/vendor
|
||||
Generated
-3665
File diff suppressed because it is too large
Load Diff
Vendored
+4
-4
@@ -1607,7 +1607,7 @@ trix-editor .attachment__metadata .attachment__size {
|
||||
|
||||
/* packages/forms/resources/css/components/select.css */
|
||||
.choices {
|
||||
@apply relative overflow-hidden;
|
||||
@apply relative;
|
||||
}
|
||||
.choices:focus {
|
||||
@apply outline-none;
|
||||
@@ -1643,14 +1643,14 @@ trix-editor .attachment__metadata .attachment__size {
|
||||
.choices__inner {
|
||||
@apply py-2 px-3 inline-block align-top w-full bg-white bg-no-repeat border border-gray-300 transition duration-75 rounded-lg shadow-sm;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
||||
background-position: right .5rem center;
|
||||
background-position: right 0.5rem center;
|
||||
background-size: 1.5em 1.5em;
|
||||
}
|
||||
.dark .choices__inner {
|
||||
@apply border-gray-600 bg-gray-700 text-white;
|
||||
}
|
||||
[dir=rtl] .choices__inner {
|
||||
background-position: left .5rem center;
|
||||
background-position: left 0.5rem center;
|
||||
}
|
||||
.is-focused .choices__inner,
|
||||
.is-open .choices__inner {
|
||||
@@ -1791,7 +1791,7 @@ trix-editor .attachment__metadata .attachment__size {
|
||||
border-color: rgb(75 85 99) !important;
|
||||
}
|
||||
.choices__input {
|
||||
@apply inline-block border-0 max-w-full;
|
||||
@apply inline-block max-w-full border-none;
|
||||
border-color: transparent !important;
|
||||
background-color: transparent !important;
|
||||
padding: 0 !important;
|
||||
|
||||
Vendored
+5925
-2410
File diff suppressed because it is too large
Load Diff
@@ -228,6 +228,12 @@ If you wish to translate the package, you may publish the language files using:
|
||||
php artisan vendor:publish --tag=forms-translations
|
||||
```
|
||||
|
||||
Since this package depends on other Filament packages, you may wish to translate those as well:
|
||||
|
||||
```bash
|
||||
php artisan vendor:publish --tag=filament-support-translations
|
||||
```
|
||||
|
||||
## Upgrading
|
||||
|
||||
To upgrade the package to the latest version, you must run:
|
||||
|
||||
@@ -281,8 +281,6 @@ TextInput::make('name')
|
||||
->maxLength(255)
|
||||
```
|
||||
|
||||
> When using `minLength()` or `maxLength()` with `numeric()`, be aware that the validation will apply to the value of the input instead of its length. This is in line with the behaviour of Laravel's `min` and `max` validation rules.
|
||||
|
||||
You may also specify the exact length of the input by setting the `length()`. This method adds both frontend and backend validation:
|
||||
|
||||
```php
|
||||
@@ -291,8 +289,6 @@ use Filament\Forms\Components\TextInput;
|
||||
TextInput::make('code')->length(8)
|
||||
```
|
||||
|
||||
Internally, this uses the `size` rule by default, and the `digits` rule for numeric inputs.
|
||||
|
||||
In addition, you may validate the minimum and maximum value of the input by setting the `minValue()` and `maxValue()` methods:
|
||||
|
||||
```php
|
||||
@@ -568,7 +564,7 @@ Since HTML does not support nested `<form>` elements, you must also render the m
|
||||
```blade
|
||||
<form wire:submit.prevent="submit">
|
||||
{{ $this->form }}
|
||||
|
||||
|
||||
<button type="submit">
|
||||
Submit
|
||||
</button>
|
||||
@@ -766,6 +762,16 @@ Toggle::make('is_admin')
|
||||
->offIcon('heroicon-s-user')
|
||||
```
|
||||
|
||||
You may also customize the color representing each state. These may be either `primary`, `secondary`, `success`, `warning` or `danger`:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\Toggle;
|
||||
|
||||
Toggle::make('is_admin')
|
||||
->onColor('success')
|
||||
->offColor('danger')
|
||||
```
|
||||
|
||||

|
||||
|
||||
If you're saving the boolean value using Eloquent, you should be sure to add a `boolean` [cast](https://laravel.com/docs/eloquent-mutators#attribute-casting) to the model property:
|
||||
@@ -1076,6 +1082,18 @@ FileUpload::make('attachment')
|
||||
})
|
||||
```
|
||||
|
||||
You can keep the randomly generated file names, while still storing the original file name, using the `storeFileNamesIn()` method:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
|
||||
FileUpload::make('attachments')
|
||||
->multiple()
|
||||
->storeFileNamesIn('attachment_file_names')
|
||||
```
|
||||
|
||||
`attachment_file_names` will now store the original file name/s of your uploaded files.
|
||||
|
||||
You may restrict the types of files that may be uploaded using the `acceptedFileTypes()` method, and passing an array of MIME types. You may also use the `image()` method as shorthand to allow all image MIME types.
|
||||
|
||||
```php
|
||||
@@ -1535,6 +1553,34 @@ Repeater::make('members')
|
||||
|
||||
Any fields that you use from `$state` should be `reactive()` or `lazy()` if you wish to see the item label update live as you use the form.
|
||||
|
||||
### Using `$get()` to access parent field values
|
||||
|
||||
All form components are able to [use `$get()` and `$set()`](advanced) to access another field's value. However, you might experience unexpected behaviour when using this inside the repeater's schema.
|
||||
|
||||
This is because `$get()` and `$set()`, by default, are scoped to the current repeater item. This means that you are able to interact with another field inside that repeater item easily without knowing which repeater item the current form component belongs to.
|
||||
|
||||
The consequence of this, is that you may be confused when you are unable to interact with a field outside the repeater. We use `../` syntax to solve this problem - `$get('../../parent_field_name')`.
|
||||
|
||||
Consider your form has this data structure:
|
||||
|
||||
```php
|
||||
[
|
||||
'client_id' => 1,
|
||||
|
||||
'repeater' => [
|
||||
'item1' => [
|
||||
'service_id' => 2,
|
||||
],
|
||||
],
|
||||
]
|
||||
```
|
||||
|
||||
You are trying to retrieve the value of `client_id` from inside the repeater item.
|
||||
|
||||
`$get()` is relative to the current repeater item, so `$get('client_id')` is looking for `$get('repeater.item1.client_id')`.
|
||||
|
||||
You can use `../` to go up a level in the data structure, so `$get('../client_id')` is $get('repeater.client_id') and `$get('../../client_id')` is `$get('client_id')`.
|
||||
|
||||
## Builder
|
||||
|
||||
Similar to a [repeater](#repeater), the builder component allows you to output a JSON array of repeated form components. Unlike the repeater, which only defines one form schema to repeat, the builder allows you to define different schema "blocks", which you can repeat in any order. This makes it useful for building more advanced array structures.
|
||||
@@ -1786,6 +1832,15 @@ KeyValue::make('meta')
|
||||
->disableEditingKeys()
|
||||
```
|
||||
|
||||
You can allow the user to reorder rows within the table:
|
||||
|
||||
```php
|
||||
use Filament\Forms\Components\KeyValue;
|
||||
|
||||
KeyValue::make('meta')
|
||||
->reorderable()
|
||||
```
|
||||
|
||||
You may also add placeholders for the key and value fields using the `keyPlaceholder()` and `valuePlaceholder()` methods:
|
||||
|
||||
```php
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
.filament-forms-color-picker-preview {
|
||||
background-image: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa);
|
||||
background-image: repeating-linear-gradient(
|
||||
45deg,
|
||||
#aaa 25%,
|
||||
transparent 25%,
|
||||
transparent 75%,
|
||||
#aaa 75%,
|
||||
#aaa
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
45deg,
|
||||
#aaa 25%,
|
||||
#fff 25%,
|
||||
#fff 75%,
|
||||
#aaa 75%,
|
||||
#aaa
|
||||
);
|
||||
background-position: 0 0, 4px 4px;
|
||||
background-size: 8px 8px;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.choices {
|
||||
@apply relative overflow-hidden;
|
||||
@apply relative;
|
||||
}
|
||||
|
||||
.choices:focus {
|
||||
@@ -27,26 +27,26 @@
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.choices[data-type*=select-one] {
|
||||
.choices[data-type*='select-one'] {
|
||||
@apply cursor-pointer;
|
||||
}
|
||||
|
||||
.choices[data-type*=select-one] .choices__input {
|
||||
.choices[data-type*='select-one'] .choices__input {
|
||||
@apply block w-full p-2 border-b m-0;
|
||||
}
|
||||
|
||||
.dark .choices[data-type*=select-one] .choices__input {
|
||||
.dark .choices[data-type*='select-one'] .choices__input {
|
||||
@apply bg-gray-700;
|
||||
}
|
||||
|
||||
.choices[data-type*=select-multiple] .choices__inner {
|
||||
.choices[data-type*='select-multiple'] .choices__inner {
|
||||
@apply cursor-text;
|
||||
}
|
||||
|
||||
.choices__inner {
|
||||
@apply py-2 px-3 inline-block align-top w-full bg-white bg-no-repeat border border-gray-300 transition duration-75 rounded-lg shadow-sm;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3E%3Cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3E%3C/svg%3E");
|
||||
background-position: right .5rem center;
|
||||
background-position: right 0.5rem center;
|
||||
background-size: 1.5em 1.5em;
|
||||
}
|
||||
|
||||
@@ -54,11 +54,12 @@
|
||||
@apply border-gray-600 bg-gray-700 text-white;
|
||||
}
|
||||
|
||||
[dir=rtl] .choices__inner {
|
||||
background-position: left .5rem center;
|
||||
[dir='rtl'] .choices__inner {
|
||||
background-position: left 0.5rem center;
|
||||
}
|
||||
|
||||
.is-focused .choices__inner, .is-open .choices__inner {
|
||||
.is-focused .choices__inner,
|
||||
.is-open .choices__inner {
|
||||
@apply border-primary-600 ring-1 ring-inset ring-primary-600;
|
||||
}
|
||||
|
||||
@@ -70,7 +71,7 @@
|
||||
@apply pr-12 inline-block w-full;
|
||||
}
|
||||
|
||||
[dir=rtl] .choices__list--single {
|
||||
[dir='rtl'] .choices__list--single {
|
||||
@apply pl-12 pr-0;
|
||||
}
|
||||
|
||||
@@ -82,7 +83,7 @@
|
||||
@apply pr-6 flex flex-wrap gap-1;
|
||||
}
|
||||
|
||||
[dir=rtl] .choices__list--multiple {
|
||||
[dir='rtl'] .choices__list--multiple {
|
||||
@apply pl-6 pr-0;
|
||||
}
|
||||
|
||||
@@ -98,40 +99,47 @@
|
||||
@apply text-primary-500;
|
||||
}
|
||||
|
||||
[dir=rtl] .choices__list--multiple .choices__item {
|
||||
[dir='rtl'] .choices__list--multiple .choices__item {
|
||||
@apply break-normal space-x-reverse;
|
||||
}
|
||||
|
||||
.choices__list--dropdown, .choices__list[aria-expanded] {
|
||||
.choices__list--dropdown,
|
||||
.choices__list[aria-expanded] {
|
||||
@apply mt-2 rounded-lg invisible z-[1] absolute w-full bg-white border border-gray-300 shadow-sm top-full overflow-hidden break-words will-change-[visibility];
|
||||
}
|
||||
|
||||
.dark .choices__list--dropdown, .dark .choices__list[aria-expanded] {
|
||||
.dark .choices__list--dropdown,
|
||||
.dark .choices__list[aria-expanded] {
|
||||
@apply bg-gray-700 border-gray-600;
|
||||
}
|
||||
|
||||
.is-active.choices__list--dropdown, .is-active.choices__list[aria-expanded] {
|
||||
.is-active.choices__list--dropdown,
|
||||
.is-active.choices__list[aria-expanded] {
|
||||
@apply visible z-10;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__list, .choices__list[aria-expanded] .choices__list {
|
||||
.choices__list--dropdown .choices__list,
|
||||
.choices__list[aria-expanded] .choices__list {
|
||||
@apply relative max-h-60 overflow-auto will-change-scroll;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item, .choices__list[aria-expanded] .choices__item {
|
||||
.choices__list--dropdown .choices__item,
|
||||
.choices__list[aria-expanded] .choices__item {
|
||||
@apply relative px-3 py-2;
|
||||
}
|
||||
|
||||
[dir=rtl] .choices__list--dropdown .choices__item,
|
||||
[dir=rtl] .choices__list[aria-expanded] .choices__item {
|
||||
[dir='rtl'] .choices__list--dropdown .choices__item,
|
||||
[dir='rtl'] .choices__list[aria-expanded] .choices__item {
|
||||
@apply text-right;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted,
|
||||
.choices__list[aria-expanded] .choices__item--selectable.is-highlighted {
|
||||
@apply bg-primary-600 text-white;
|
||||
}
|
||||
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted::after, .choices__list[aria-expanded] .choices__item--selectable.is-highlighted::after {
|
||||
.choices__list--dropdown .choices__item--selectable.is-highlighted::after,
|
||||
.choices__list[aria-expanded] .choices__item--selectable.is-highlighted::after {
|
||||
@apply opacity-70;
|
||||
}
|
||||
|
||||
@@ -160,54 +168,56 @@
|
||||
@apply outline-none;
|
||||
}
|
||||
|
||||
.choices[data-type*=select-one] .choices__button {
|
||||
.choices[data-type*='select-one'] .choices__button {
|
||||
@apply p-0 absolute right-0 mr-9 h-4 w-4 opacity-60;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
background-size: 0.7em 0.7em;
|
||||
top: calc(50% - 0.55em);
|
||||
}
|
||||
|
||||
.dark .choices[data-type*=select-one] .choices__button {
|
||||
.dark .choices[data-type*='select-one'] .choices__button {
|
||||
@apply opacity-30;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
}
|
||||
|
||||
[dir=rtl] .choices[data-type*=select-one] .choices__button {
|
||||
[dir='rtl'] .choices[data-type*='select-one'] .choices__button {
|
||||
@apply left-0 right-auto ml-9 mr-0;
|
||||
}
|
||||
|
||||
.choices[data-type*=select-one] .choices__button:hover,
|
||||
.choices[data-type*=select-one] .choices__button:focus {
|
||||
.choices[data-type*='select-one'] .choices__button:hover,
|
||||
.choices[data-type*='select-one'] .choices__button:focus {
|
||||
@apply opacity-75;
|
||||
}
|
||||
|
||||
.dark .choices[data-type*=select-one] .choices__button:hover,
|
||||
.dark .choices[data-type*=select-one] .choices__button:focus {
|
||||
.dark .choices[data-type*='select-one'] .choices__button:hover,
|
||||
.dark .choices[data-type*='select-one'] .choices__button:focus {
|
||||
@apply opacity-60;
|
||||
}
|
||||
|
||||
.choices[data-type*=select-one] .choices__item[data-value=""] .choices__button {
|
||||
.choices[data-type*='select-one']
|
||||
.choices__item[data-value='']
|
||||
.choices__button {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.choices[data-type*=select-multiple] .choices__button {
|
||||
.choices[data-type*='select-multiple'] .choices__button {
|
||||
@apply text-primary-700 inline-block w-3 h-3 opacity-60;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
background-size: 0.6em 0.6em;
|
||||
}
|
||||
|
||||
.dark .choices[data-type*=select-multiple] .choices__button {
|
||||
.dark .choices[data-type*='select-multiple'] .choices__button {
|
||||
@apply opacity-75;
|
||||
background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==");
|
||||
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjEiIGhlaWdodD0iMjEiIHZpZXdCb3g9IjAgMCAyMSAyMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjZmZmIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPjxwYXRoIGQ9Ik0yLjU5Mi4wNDRsMTguMzY0IDE4LjM2NC0yLjU0OCAyLjU0OEwuMDQ0IDIuNTkyeiIvPjxwYXRoIGQ9Ik0wIDE4LjM2NEwxOC4zNjQgMGwyLjU0OCAyLjU0OEwyLjU0OCAyMC45MTJ6Ii8+PC9nPjwvc3ZnPg==');
|
||||
}
|
||||
|
||||
.choices[data-type*=select-multiple] .choices__button:hover,
|
||||
.choices[data-type*=select-multiple] .choices__button:focus {
|
||||
.choices[data-type*='select-multiple'] .choices__button:hover,
|
||||
.choices[data-type*='select-multiple'] .choices__button:focus {
|
||||
@apply opacity-75;
|
||||
}
|
||||
|
||||
.dark .choices[data-type*=select-multiple] .choices__button:hover,
|
||||
.dark .choices[data-type*=select-multiple] .choices__button:focus {
|
||||
.dark .choices[data-type*='select-multiple'] .choices__button:hover,
|
||||
.dark .choices[data-type*='select-multiple'] .choices__button:focus {
|
||||
@apply opacity-100;
|
||||
}
|
||||
|
||||
@@ -228,7 +238,7 @@
|
||||
}
|
||||
|
||||
.choices__input {
|
||||
@apply inline-block border-0 max-w-full;
|
||||
@apply inline-block max-w-full border-none;
|
||||
border-color: transparent !important;
|
||||
background-color: transparent !important;
|
||||
padding: 0 !important;
|
||||
@@ -236,13 +246,18 @@
|
||||
|
||||
.choices__input:focus {
|
||||
outline-color: transparent !important;
|
||||
box-shadow: var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color) !important;
|
||||
box-shadow: var(--tw-ring-inset) 0 0 0
|
||||
calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color) !important;
|
||||
}
|
||||
|
||||
.choices__input::-webkit-search-decoration, .choices__input::-webkit-search-cancel-button, .choices__input::-webkit-search-results-button, .choices__input::-webkit-search-results-decoration {
|
||||
.choices__input::-webkit-search-decoration,
|
||||
.choices__input::-webkit-search-cancel-button,
|
||||
.choices__input::-webkit-search-results-button,
|
||||
.choices__input::-webkit-search-results-decoration {
|
||||
@apply hidden;
|
||||
}
|
||||
|
||||
.choices__input::-ms-clear, .choices__input::-ms-reveal {
|
||||
.choices__input::-ms-clear,
|
||||
.choices__input::-ms-reveal {
|
||||
@apply hidden w-0 h-0;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export default (Alpine) => {
|
||||
'color-changed',
|
||||
(event) => {
|
||||
this.setState(event.detail.value)
|
||||
}
|
||||
},
|
||||
)
|
||||
},
|
||||
|
||||
@@ -49,6 +49,6 @@ export default (Alpine) => {
|
||||
this.$refs.panel.color = value
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,20 +8,6 @@ dayjs.extend(customParseFormat)
|
||||
dayjs.extend(localeData)
|
||||
dayjs.extend(timezone)
|
||||
dayjs.extend(utc)
|
||||
dayjs.extend((option, Dayjs, dayjs) => {
|
||||
const listeners = []
|
||||
|
||||
dayjs.addLocaleListeners = (listener) => listeners.push(listener)
|
||||
dayjs.onLocaleUpdated = () => {
|
||||
listeners.forEach((listener) => listener())
|
||||
}
|
||||
dayjs.updateLocale = (locale) => {
|
||||
dayjs.locale(locale)
|
||||
|
||||
// Emit the `localeUpdated` event that we can bind to later
|
||||
dayjs.onLocaleUpdated()
|
||||
}
|
||||
})
|
||||
|
||||
window.dayjs = dayjs
|
||||
|
||||
@@ -33,6 +19,7 @@ export default (Alpine) => {
|
||||
firstDayOfWeek,
|
||||
isAutofocused,
|
||||
isDisabled,
|
||||
locale,
|
||||
shouldCloseOnDateSelection,
|
||||
state,
|
||||
}) => {
|
||||
@@ -66,6 +53,8 @@ export default (Alpine) => {
|
||||
months: [],
|
||||
|
||||
init: function () {
|
||||
dayjs.locale(locales[locale] ?? locales['en'])
|
||||
|
||||
this.focusedDate = dayjs().tz(timezone)
|
||||
|
||||
let date =
|
||||
@@ -94,16 +83,10 @@ export default (Alpine) => {
|
||||
|
||||
if (isAutofocused) {
|
||||
this.$nextTick(() =>
|
||||
this.togglePanelVisibility(this.$refs.button)
|
||||
this.togglePanelVisibility(this.$refs.button),
|
||||
)
|
||||
}
|
||||
|
||||
dayjs.addLocaleListeners(() => {
|
||||
this.setDisplayText()
|
||||
this.setMonths()
|
||||
this.setDayLabels()
|
||||
})
|
||||
|
||||
this.$watch('focusedMonth', () => {
|
||||
this.focusedMonth = +this.focusedMonth
|
||||
|
||||
@@ -112,7 +95,7 @@ export default (Alpine) => {
|
||||
}
|
||||
|
||||
this.focusedDate = this.focusedDate.month(
|
||||
this.focusedMonth
|
||||
this.focusedMonth,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -432,14 +415,14 @@ export default (Alpine) => {
|
||||
.date(8 - firstDayOfWeek)
|
||||
.day(),
|
||||
},
|
||||
(_, i) => i + 1
|
||||
(_, i) => i + 1,
|
||||
)
|
||||
|
||||
this.daysInFocusedMonth = Array.from(
|
||||
{
|
||||
length: this.focusedDate.daysInMonth(),
|
||||
},
|
||||
(_, i) => i + 1
|
||||
(_, i) => i + 1,
|
||||
)
|
||||
},
|
||||
|
||||
@@ -474,6 +457,41 @@ export default (Alpine) => {
|
||||
return this.$refs.panel.style.display === 'block'
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
const locales = {
|
||||
ar: require('dayjs/locale/ar'),
|
||||
bs: require('dayjs/locale/bs'),
|
||||
ca: require('dayjs/locale/ca'),
|
||||
cs: require('dayjs/locale/cs'),
|
||||
da: require('dayjs/locale/da'),
|
||||
de: require('dayjs/locale/de'),
|
||||
en: require('dayjs/locale/en'),
|
||||
es: require('dayjs/locale/es'),
|
||||
fa: require('dayjs/locale/fa'),
|
||||
fr: require('dayjs/locale/fr'),
|
||||
hi: require('dayjs/locale/hi'),
|
||||
hu: require('dayjs/locale/hu'),
|
||||
hy: require('dayjs/locale/hy-am'),
|
||||
id: require('dayjs/locale/id'),
|
||||
it: require('dayjs/locale/it'),
|
||||
js: require('dayjs/locale/ja'),
|
||||
ka: require('dayjs/locale/ka'),
|
||||
ku: require('dayjs/locale/ku'),
|
||||
ms: require('dayjs/locale/ms'),
|
||||
my: require('dayjs/locale/my'),
|
||||
nl: require('dayjs/locale/nl'),
|
||||
pl: require('dayjs/locale/pl'),
|
||||
pt_BR: require('dayjs/locale/pt-br'),
|
||||
pt_PT: require('dayjs/locale/pt'),
|
||||
ro: require('dayjs/locale/ro'),
|
||||
ru: require('dayjs/locale/ru'),
|
||||
sv: require('dayjs/locale/sv'),
|
||||
tr: require('dayjs/locale/tr'),
|
||||
uk: require('dayjs/locale/uk'),
|
||||
vi: require('dayjs/locale/vi'),
|
||||
zh_CN: require('dayjs/locale/zh-cn'),
|
||||
zh_TW: require('dayjs/locale/zh-tw'),
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import FilePondPluginMediaPreview from 'filepond-plugin-media-preview'
|
||||
|
||||
import 'filepond/dist/filepond.min.css'
|
||||
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css'
|
||||
import 'filepond-plugin-media-preview/dist/filepond-plugin-media-preview.css';
|
||||
import 'filepond-plugin-media-preview/dist/filepond-plugin-media-preview.css'
|
||||
import '../../css/components/file-upload.css'
|
||||
|
||||
FilePond.registerPlugin(FilePondPluginFileValidateSize)
|
||||
@@ -22,300 +22,404 @@ FilePond.registerPlugin(FilePondPluginImageResize)
|
||||
FilePond.registerPlugin(FilePondPluginImageTransform)
|
||||
FilePond.registerPlugin(FilePondPluginMediaPreview)
|
||||
|
||||
window.FilePond = FilePond;
|
||||
window.FilePond = FilePond
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('fileUploadFormComponent', ({
|
||||
acceptedFileTypes,
|
||||
canDownload,
|
||||
canOpen,
|
||||
canPreview,
|
||||
canReorder,
|
||||
deleteUploadedFileUsing,
|
||||
getUploadedFileUrlsUsing,
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeMode,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
isAvatar,
|
||||
loadingIndicatorPosition,
|
||||
panelAspectRatio,
|
||||
panelLayout,
|
||||
placeholder,
|
||||
maxSize,
|
||||
minSize,
|
||||
removeUploadedFileButtonPosition,
|
||||
removeUploadedFileUsing,
|
||||
reorderUploadedFilesUsing,
|
||||
shouldAppendFiles,
|
||||
shouldTransformImage,
|
||||
state,
|
||||
uploadButtonPosition,
|
||||
uploadProgressIndicatorPosition,
|
||||
uploadUsing,
|
||||
}) => {
|
||||
return {
|
||||
fileKeyIndex: {},
|
||||
|
||||
pond: null,
|
||||
|
||||
shouldUpdateState: true,
|
||||
|
||||
Alpine.data(
|
||||
'fileUploadFormComponent',
|
||||
({
|
||||
acceptedFileTypes,
|
||||
canDownload,
|
||||
canOpen,
|
||||
canPreview,
|
||||
canReorder,
|
||||
deleteUploadedFileUsing,
|
||||
getUploadedFileUrlsUsing,
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeMode,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
isAvatar,
|
||||
loadingIndicatorPosition,
|
||||
locale,
|
||||
panelAspectRatio,
|
||||
panelLayout,
|
||||
placeholder,
|
||||
maxSize,
|
||||
minSize,
|
||||
removeUploadedFileButtonPosition,
|
||||
removeUploadedFileUsing,
|
||||
reorderUploadedFilesUsing,
|
||||
shouldAppendFiles,
|
||||
shouldTransformImage,
|
||||
state,
|
||||
uploadButtonPosition,
|
||||
uploadProgressIndicatorPosition,
|
||||
uploadUsing,
|
||||
}) => {
|
||||
return {
|
||||
fileKeyIndex: {},
|
||||
|
||||
lastState: null,
|
||||
pond: null,
|
||||
|
||||
uploadedFileUrlIndex: {},
|
||||
shouldUpdateState: true,
|
||||
|
||||
init: async function () {
|
||||
this.pond = FilePond.create(this.$refs.input, {
|
||||
acceptedFileTypes,
|
||||
allowPaste: false,
|
||||
allowReorder: canReorder,
|
||||
allowImagePreview: canPreview,
|
||||
allowVideoPreview: canPreview,
|
||||
allowAudioPreview: canPreview,
|
||||
allowImageTransform: shouldTransformImage,
|
||||
credits: false,
|
||||
files: await this.getFiles(),
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
imageResizeMode,
|
||||
itemInsertLocation: shouldAppendFiles ? 'after' : 'before',
|
||||
...(placeholder && {labelIdle: placeholder}),
|
||||
maxFileSize: maxSize,
|
||||
minFileSize: minSize,
|
||||
styleButtonProcessItemPosition: uploadButtonPosition,
|
||||
styleButtonRemoveItemPosition: removeUploadedFileButtonPosition,
|
||||
styleLoadIndicatorPosition: loadingIndicatorPosition,
|
||||
stylePanelAspectRatio: panelAspectRatio,
|
||||
stylePanelLayout: panelLayout,
|
||||
styleProgressIndicatorPosition: uploadProgressIndicatorPosition,
|
||||
server: {
|
||||
load: async (source, load) => {
|
||||
let response = await fetch(source)
|
||||
let blob = await response.blob()
|
||||
state,
|
||||
|
||||
load(blob)
|
||||
},
|
||||
process: (fieldName, file, metadata, load, error, progress) => {
|
||||
this.shouldUpdateState = false
|
||||
lastState: null,
|
||||
|
||||
let fileKey = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
||||
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
||||
)
|
||||
uploadedFileUrlIndex: {},
|
||||
|
||||
uploadUsing(fileKey, file, (fileKey) => {
|
||||
this.shouldUpdateState = true
|
||||
init: async function () {
|
||||
FilePond.setOptions(locales[locale] ?? locales['en'])
|
||||
|
||||
load(fileKey)
|
||||
}, error, progress)
|
||||
},
|
||||
remove: async (source, load) => {
|
||||
let fileKey = this.uploadedFileUrlIndex[source] ?? null
|
||||
this.pond = FilePond.create(this.$refs.input, {
|
||||
acceptedFileTypes,
|
||||
allowPaste: false,
|
||||
allowReorder: canReorder,
|
||||
allowImagePreview: canPreview,
|
||||
allowVideoPreview: canPreview,
|
||||
allowAudioPreview: canPreview,
|
||||
allowImageTransform: shouldTransformImage,
|
||||
credits: false,
|
||||
files: await this.getFiles(),
|
||||
imageCropAspectRatio,
|
||||
imagePreviewHeight,
|
||||
imageResizeTargetHeight,
|
||||
imageResizeTargetWidth,
|
||||
imageResizeMode,
|
||||
itemInsertLocation: shouldAppendFiles
|
||||
? 'after'
|
||||
: 'before',
|
||||
...(placeholder && { labelIdle: placeholder }),
|
||||
maxFileSize: maxSize,
|
||||
minFileSize: minSize,
|
||||
styleButtonProcessItemPosition: uploadButtonPosition,
|
||||
styleButtonRemoveItemPosition:
|
||||
removeUploadedFileButtonPosition,
|
||||
styleLoadIndicatorPosition: loadingIndicatorPosition,
|
||||
stylePanelAspectRatio: panelAspectRatio,
|
||||
stylePanelLayout: panelLayout,
|
||||
styleProgressIndicatorPosition:
|
||||
uploadProgressIndicatorPosition,
|
||||
server: {
|
||||
load: async (source, load) => {
|
||||
let response = await fetch(source)
|
||||
let blob = await response.blob()
|
||||
|
||||
if (! fileKey) {
|
||||
return
|
||||
}
|
||||
load(blob)
|
||||
},
|
||||
process: (
|
||||
fieldName,
|
||||
file,
|
||||
metadata,
|
||||
load,
|
||||
error,
|
||||
progress,
|
||||
) => {
|
||||
this.shouldUpdateState = false
|
||||
|
||||
await deleteUploadedFileUsing(fileKey)
|
||||
let fileKey = (
|
||||
[1e7] +
|
||||
-1e3 +
|
||||
-4e3 +
|
||||
-8e3 +
|
||||
-1e11
|
||||
).replace(/[018]/g, (c) =>
|
||||
(
|
||||
c ^
|
||||
(crypto.getRandomValues(
|
||||
new Uint8Array(1),
|
||||
)[0] &
|
||||
(15 >> (c / 4)))
|
||||
).toString(16),
|
||||
)
|
||||
|
||||
load()
|
||||
},
|
||||
revert: async (uniqueFileId, load) => {
|
||||
await removeUploadedFileUsing(uniqueFileId)
|
||||
uploadUsing(
|
||||
fileKey,
|
||||
file,
|
||||
(fileKey) => {
|
||||
this.shouldUpdateState = true
|
||||
|
||||
load()
|
||||
},
|
||||
},
|
||||
})
|
||||
load(fileKey)
|
||||
},
|
||||
error,
|
||||
progress,
|
||||
)
|
||||
},
|
||||
remove: async (source, load) => {
|
||||
let fileKey =
|
||||
this.uploadedFileUrlIndex[source] ?? null
|
||||
|
||||
this.$watch('state', async () => {
|
||||
if (! this.shouldUpdateState) {
|
||||
return;
|
||||
}
|
||||
if (!fileKey) {
|
||||
return
|
||||
}
|
||||
|
||||
// We don't want to overwrite the files that are already in the input, if they haven't been saved yet.
|
||||
if (Object.values(this.state).filter((file) => file.startsWith('livewire-file:')).length) {
|
||||
return
|
||||
}
|
||||
await deleteUploadedFileUsing(fileKey)
|
||||
|
||||
// Don't do anything if the state hasn't changed
|
||||
if (JSON.stringify(this.state) === this.lastState) {
|
||||
return
|
||||
}
|
||||
load()
|
||||
},
|
||||
revert: async (uniqueFileId, load) => {
|
||||
await removeUploadedFileUsing(uniqueFileId)
|
||||
|
||||
this.lastState = JSON.stringify(this.state)
|
||||
|
||||
this.pond.files = await this.getFiles()
|
||||
})
|
||||
|
||||
this.pond.on('reorderfiles', async (files) => {
|
||||
const orderedFileKeys = files
|
||||
.map(file => file.source instanceof File ? file.serverId : this.uploadedFileUrlIndex[file.source] ?? null) // file.serverId is null for a file that is not yet uploaded
|
||||
.filter(fileKey => fileKey)
|
||||
|
||||
await reorderUploadedFilesUsing(shouldAppendFiles ? orderedFileKeys : orderedFileKeys.reverse())
|
||||
})
|
||||
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (! canDownload) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
this.insertDownloadLink(fileItem)
|
||||
})
|
||||
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (! canOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
this.insertOpenLink(fileItem)
|
||||
})
|
||||
|
||||
this.pond.on('processfilestart', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
this.pond.on('processfileprogress', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
this.pond.on('processfile', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfiles', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfileabort', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfilerevert', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
},
|
||||
|
||||
dispatchFormEvent: function (name) {
|
||||
this.$el.closest('form')?.dispatchEvent(
|
||||
new CustomEvent(name, {
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
})
|
||||
)
|
||||
},
|
||||
|
||||
getUploadedFileUrls: async function () {
|
||||
const uploadedFileUrls = await getUploadedFileUrlsUsing()
|
||||
|
||||
this.fileKeyIndex = uploadedFileUrls ?? {}
|
||||
|
||||
this.uploadedFileUrlIndex = Object.entries(this.fileKeyIndex)
|
||||
.filter(value => value)
|
||||
.reduce((obj, [key, value]) => {
|
||||
obj[value] = key
|
||||
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
|
||||
getFiles: async function () {
|
||||
await this.getUploadedFileUrls()
|
||||
|
||||
let files = []
|
||||
|
||||
for (const uploadedFileUrl of Object.values(this.fileKeyIndex)) {
|
||||
if (! uploadedFileUrl) {
|
||||
continue
|
||||
}
|
||||
|
||||
files.push({
|
||||
source: uploadedFileUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
load()
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return shouldAppendFiles ? files : files.reverse()
|
||||
},
|
||||
this.$watch('state', async () => {
|
||||
if (!this.shouldUpdateState) {
|
||||
return
|
||||
}
|
||||
|
||||
insertDownloadLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
// We don't want to overwrite the files that are already in the input, if they haven't been saved yet.
|
||||
if (
|
||||
Object.values(this.state).filter((file) =>
|
||||
file.startsWith('livewire-file:'),
|
||||
).length
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = this.getDownloadLink(file)
|
||||
// Don't do anything if the state hasn't changed
|
||||
if (JSON.stringify(this.state) === this.lastState) {
|
||||
return
|
||||
}
|
||||
|
||||
if (! anchor) {
|
||||
return
|
||||
}
|
||||
this.lastState = JSON.stringify(this.state)
|
||||
|
||||
document.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
this.pond.files = await this.getFiles()
|
||||
})
|
||||
|
||||
this.pond.on('reorderfiles', async (files) => {
|
||||
const orderedFileKeys = files
|
||||
.map((file) =>
|
||||
file.source instanceof File
|
||||
? file.serverId
|
||||
: this.uploadedFileUrlIndex[file.source] ??
|
||||
null,
|
||||
) // file.serverId is null for a file that is not yet uploaded
|
||||
.filter((fileKey) => fileKey)
|
||||
|
||||
insertOpenLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
await reorderUploadedFilesUsing(
|
||||
shouldAppendFiles
|
||||
? orderedFileKeys
|
||||
: orderedFileKeys.reverse(),
|
||||
)
|
||||
})
|
||||
|
||||
const anchor = this.getOpenLink(file)
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!canDownload) {
|
||||
return
|
||||
}
|
||||
|
||||
if (! anchor) {
|
||||
return
|
||||
}
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
document.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
this.insertDownloadLink(fileItem)
|
||||
})
|
||||
|
||||
getDownloadLink: function (file) {
|
||||
let fileSource = file.source
|
||||
this.pond.on('initfile', async (fileItem) => {
|
||||
if (!canOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
if (! fileSource) {
|
||||
return
|
||||
}
|
||||
if (isAvatar) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--download-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.download = file.file.name
|
||||
this.insertOpenLink(fileItem)
|
||||
})
|
||||
|
||||
return anchor
|
||||
},
|
||||
this.pond.on('processfilestart', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
getOpenLink: function (file) {
|
||||
let fileSource = file.source
|
||||
this.pond.on('processfileprogress', async () => {
|
||||
this.dispatchFormEvent('file-upload-started')
|
||||
})
|
||||
|
||||
if (! fileSource) {
|
||||
return
|
||||
}
|
||||
this.pond.on('processfile', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--open-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.target = '_blank'
|
||||
this.pond.on('processfiles', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
return anchor
|
||||
this.pond.on('processfileabort', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
|
||||
this.pond.on('processfilerevert', async () => {
|
||||
this.dispatchFormEvent('file-upload-finished')
|
||||
})
|
||||
},
|
||||
|
||||
dispatchFormEvent: function (name) {
|
||||
this.$el.closest('form')?.dispatchEvent(
|
||||
new CustomEvent(name, {
|
||||
composed: true,
|
||||
cancelable: true,
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
getUploadedFileUrls: async function () {
|
||||
const uploadedFileUrls = await getUploadedFileUrlsUsing()
|
||||
|
||||
this.fileKeyIndex = uploadedFileUrls ?? {}
|
||||
|
||||
this.uploadedFileUrlIndex = Object.entries(
|
||||
this.fileKeyIndex,
|
||||
)
|
||||
.filter((value) => value)
|
||||
.reduce((obj, [key, value]) => {
|
||||
obj[value] = key
|
||||
|
||||
return obj
|
||||
}, {})
|
||||
},
|
||||
|
||||
getFiles: async function () {
|
||||
await this.getUploadedFileUrls()
|
||||
|
||||
let files = []
|
||||
|
||||
for (const uploadedFileUrl of Object.values(
|
||||
this.fileKeyIndex,
|
||||
)) {
|
||||
if (!uploadedFileUrl) {
|
||||
continue
|
||||
}
|
||||
|
||||
files.push({
|
||||
source: uploadedFileUrl,
|
||||
options: {
|
||||
type: 'local',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return shouldAppendFiles ? files : files.reverse()
|
||||
},
|
||||
|
||||
insertDownloadLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = this.getDownloadLink(file)
|
||||
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
|
||||
insertOpenLink: function (file) {
|
||||
if (file.origin !== FilePond.FileOrigin.LOCAL) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = this.getOpenLink(file)
|
||||
|
||||
if (!anchor) {
|
||||
return
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById(`filepond--item-${file.id}`)
|
||||
.querySelector('.filepond--file-info-main')
|
||||
.prepend(anchor)
|
||||
},
|
||||
|
||||
getDownloadLink: function (file) {
|
||||
let fileSource = file.source
|
||||
|
||||
if (!fileSource) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--download-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.download = file.file.name
|
||||
|
||||
return anchor
|
||||
},
|
||||
|
||||
getOpenLink: function (file) {
|
||||
let fileSource = file.source
|
||||
|
||||
if (!fileSource) {
|
||||
return
|
||||
}
|
||||
|
||||
const anchor = document.createElement('a')
|
||||
anchor.className = 'filepond--open-icon'
|
||||
anchor.href = fileSource
|
||||
anchor.target = '_blank'
|
||||
|
||||
return anchor
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
import ar from 'filepond/locale/ar-ar'
|
||||
import cs from 'filepond/locale/cs-cz'
|
||||
import da from 'filepond/locale/da-dk'
|
||||
import de from 'filepond/locale/de-de'
|
||||
import en from 'filepond/locale/en-en'
|
||||
import es from 'filepond/locale/es-es'
|
||||
import fa from 'filepond/locale/fa_ir'
|
||||
import fr from 'filepond/locale/fr-fr'
|
||||
import hu from 'filepond/locale/hu-hu'
|
||||
import id from 'filepond/locale/id-id'
|
||||
import it from 'filepond/locale/it-it'
|
||||
import nl from 'filepond/locale/nl-nl'
|
||||
import pl from 'filepond/locale/pl-pl'
|
||||
import pt_BR from 'filepond/locale/pt-br'
|
||||
import pt_PT from 'filepond/locale/pt-br'
|
||||
import ro from 'filepond/locale/ro-ro'
|
||||
import ru from 'filepond/locale/ru-ru'
|
||||
import sv from 'filepond/locale/sv_se'
|
||||
import tr from 'filepond/locale/tr-tr'
|
||||
import uk from 'filepond/locale/uk-ua'
|
||||
import vi from 'filepond/locale/vi-vi'
|
||||
import zh_CN from 'filepond/locale/zh-cn'
|
||||
import zh_TW from 'filepond/locale/zh-tw'
|
||||
|
||||
const locales = {
|
||||
ar,
|
||||
cs,
|
||||
da,
|
||||
de,
|
||||
en,
|
||||
es,
|
||||
fa,
|
||||
fr,
|
||||
hu,
|
||||
id,
|
||||
it,
|
||||
nl,
|
||||
pl,
|
||||
pt_BR,
|
||||
pt_PT,
|
||||
ro,
|
||||
ru,
|
||||
sv,
|
||||
tr,
|
||||
uk,
|
||||
vi,
|
||||
zh_CN,
|
||||
zh_TW,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
export default (Alpine) => {
|
||||
Alpine.data('keyValueFormComponent', ({
|
||||
state,
|
||||
}) => ({
|
||||
Alpine.data('keyValueFormComponent', ({ state }) => ({
|
||||
state,
|
||||
|
||||
rows: [],
|
||||
@@ -18,7 +16,7 @@ export default (Alpine) => {
|
||||
this.shouldUpdateRows = true
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (! this.shouldUpdateRows) {
|
||||
if (!this.shouldUpdateRows) {
|
||||
this.shouldUpdateRows = true
|
||||
|
||||
return
|
||||
@@ -46,6 +44,17 @@ export default (Alpine) => {
|
||||
this.shouldUpdateRows = true
|
||||
},
|
||||
|
||||
reorderRows: function (event) {
|
||||
const rows = Alpine.raw(this.rows)
|
||||
|
||||
const reorderedRow = rows.splice(event.oldIndex, 1)[0]
|
||||
rows.splice(event.newIndex, 0, reorderedRow)
|
||||
|
||||
this.rows = rows
|
||||
|
||||
this.updateState()
|
||||
},
|
||||
|
||||
updateRows: function () {
|
||||
let rows = []
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@ import { marked } from 'marked'
|
||||
import { highlight } from 'mdhl'
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('markdownEditorFormComponent', ({
|
||||
state,
|
||||
tab,
|
||||
}) => {
|
||||
Alpine.data('markdownEditorFormComponent', ({ state, tab }) => {
|
||||
return {
|
||||
attachment: null,
|
||||
|
||||
@@ -35,7 +32,8 @@ export default (Alpine) => {
|
||||
render: function () {
|
||||
if (this.$refs.textarea.scrollHeight > 0) {
|
||||
this.$refs.overlay.style.height = '150px'
|
||||
this.$refs.overlay.style.height = this.$refs.textarea.scrollHeight + 'px'
|
||||
this.$refs.overlay.style.height =
|
||||
this.$refs.textarea.scrollHeight + 'px'
|
||||
}
|
||||
|
||||
this.overlay = null
|
||||
@@ -46,15 +44,15 @@ export default (Alpine) => {
|
||||
},
|
||||
|
||||
checkForAutoInsertion($event) {
|
||||
const lines = this.$refs.textarea.value.split("\n")
|
||||
const lines = this.$refs.textarea.value.split('\n')
|
||||
|
||||
const currentLine = this.$refs.textarea.value.substring(
|
||||
0, this.$refs.textarea.value.selectionStart
|
||||
).split("\n").length
|
||||
const currentLine = this.$refs.textarea.value
|
||||
.substring(0, this.$refs.textarea.value.selectionStart)
|
||||
.split('\n').length
|
||||
const previousLine = lines[currentLine - 2]
|
||||
|
||||
if (! previousLine.match(/^(\*\s|-\s)|^(\d)+\./)) {
|
||||
return;
|
||||
if (!previousLine.match(/^(\*\s|-\s)|^(\d)+\./)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (previousLine.match(/^(\*\s)/)) {
|
||||
@@ -73,14 +71,14 @@ export default (Alpine) => {
|
||||
const matches = previousLine.match(/^(\d)+/)
|
||||
const number = matches[0]
|
||||
|
||||
if (previousLine.trim().length > (number.length + 2)) {
|
||||
if (previousLine.trim().length > number.length + 2) {
|
||||
lines[currentLine - 1] = `${parseInt(number) + 1}. `
|
||||
} else {
|
||||
delete lines[currentLine - 2]
|
||||
}
|
||||
}
|
||||
|
||||
this.state = lines.join("\n")
|
||||
this.state = lines.join('\n')
|
||||
|
||||
this.render()
|
||||
},
|
||||
|
||||
@@ -11,35 +11,39 @@ Trix.config.blockAttributes.heading = {
|
||||
tagName: 'h2',
|
||||
terminal: true,
|
||||
breakOnReturn: true,
|
||||
group: false
|
||||
group: false,
|
||||
}
|
||||
|
||||
Trix.config.blockAttributes.subHeading = {
|
||||
tagName: 'h3',
|
||||
terminal: true,
|
||||
breakOnReturn: true,
|
||||
group: false
|
||||
group: false,
|
||||
}
|
||||
|
||||
Trix.Block.prototype.breaksOnReturn = function () {
|
||||
const lastAttribute = this.getLastAttribute()
|
||||
const blockConfig = Trix.getBlockConfig(lastAttribute ? lastAttribute : 'default')
|
||||
const blockConfig = Trix.getBlockConfig(
|
||||
lastAttribute ? lastAttribute : 'default',
|
||||
)
|
||||
|
||||
return blockConfig?.breakOnReturn ?? false
|
||||
}
|
||||
|
||||
Trix.LineBreakInsertion.prototype.shouldInsertBlockBreak = function () {
|
||||
if (this.block.hasAttributes() && this.block.isListItem() && (! this.block.isEmpty())) {
|
||||
if (
|
||||
this.block.hasAttributes() &&
|
||||
this.block.isListItem() &&
|
||||
!this.block.isEmpty()
|
||||
) {
|
||||
return this.startLocation.offset > 0
|
||||
} else {
|
||||
return (! this.shouldBreakFormattedBlock()) ? this.breaksOnReturn : false
|
||||
return !this.shouldBreakFormattedBlock() ? this.breaksOnReturn : false
|
||||
}
|
||||
}
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('richEditorFormComponent', ({
|
||||
state,
|
||||
}) => {
|
||||
Alpine.data('richEditorFormComponent', ({ state }) => {
|
||||
return {
|
||||
state,
|
||||
|
||||
@@ -53,7 +57,7 @@ export default (Alpine) => {
|
||||
|
||||
this.$refs.trix?.editor?.loadHTML(this.state)
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,224 +3,248 @@ import Choices from 'choices.js'
|
||||
import '../../css/components/select.css'
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('selectFormComponent', ({
|
||||
isHtmlAllowed,
|
||||
getOptionLabelUsing,
|
||||
getOptionLabelsUsing,
|
||||
getOptionsUsing,
|
||||
getSearchResultsUsing,
|
||||
isAutofocused,
|
||||
isMultiple,
|
||||
hasDynamicOptions,
|
||||
hasDynamicSearchResults,
|
||||
loadingMessage,
|
||||
maxItems,
|
||||
noSearchResultsMessage,
|
||||
options,
|
||||
optionsLimit,
|
||||
placeholder,
|
||||
searchingMessage,
|
||||
searchPrompt,
|
||||
state,
|
||||
}) => {
|
||||
return {
|
||||
isSearching: false,
|
||||
|
||||
select: null,
|
||||
|
||||
selectedOptions: [],
|
||||
|
||||
isStateBeingUpdated: false,
|
||||
|
||||
Alpine.data(
|
||||
'selectFormComponent',
|
||||
({
|
||||
isHtmlAllowed,
|
||||
getOptionLabelUsing,
|
||||
getOptionLabelsUsing,
|
||||
getOptionsUsing,
|
||||
getSearchResultsUsing,
|
||||
isAutofocused,
|
||||
isMultiple,
|
||||
hasDynamicOptions,
|
||||
hasDynamicSearchResults,
|
||||
loadingMessage,
|
||||
maxItems,
|
||||
noSearchResultsMessage,
|
||||
options,
|
||||
optionsLimit,
|
||||
placeholder,
|
||||
searchingMessage,
|
||||
searchPrompt,
|
||||
state,
|
||||
}) => {
|
||||
return {
|
||||
isSearching: false,
|
||||
|
||||
init: async function () {
|
||||
this.select = new Choices(this.$refs.input, {
|
||||
allowHTML: isHtmlAllowed,
|
||||
duplicateItemsAllowed: false,
|
||||
itemSelectText: '',
|
||||
loadingText: loadingMessage,
|
||||
maxItemCount: maxItems ?? -1,
|
||||
noChoicesText: searchPrompt,
|
||||
noResultsText: noSearchResultsMessage,
|
||||
placeholderValue: placeholder,
|
||||
removeItemButton: true,
|
||||
renderChoiceLimit: optionsLimit,
|
||||
searchFields: ['label'],
|
||||
searchResultLimit: optionsLimit,
|
||||
shouldSort: false,
|
||||
})
|
||||
select: null,
|
||||
|
||||
await this.refreshChoices({ withInitialOptions: true })
|
||||
selectedOptions: [],
|
||||
|
||||
if (! [null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(this.formatState(this.state))
|
||||
}
|
||||
isStateBeingUpdated: false,
|
||||
|
||||
this.refreshPlaceholder()
|
||||
state,
|
||||
|
||||
if (isAutofocused) {
|
||||
this.select.showDropdown()
|
||||
}
|
||||
init: async function () {
|
||||
this.select = new Choices(this.$refs.input, {
|
||||
allowHTML: isHtmlAllowed,
|
||||
duplicateItemsAllowed: false,
|
||||
itemSelectText: '',
|
||||
loadingText: loadingMessage,
|
||||
maxItemCount: maxItems ?? -1,
|
||||
noChoicesText: searchPrompt,
|
||||
noResultsText: noSearchResultsMessage,
|
||||
placeholderValue: placeholder,
|
||||
removeItemButton: true,
|
||||
renderChoiceLimit: optionsLimit,
|
||||
searchFields: ['label'],
|
||||
searchResultLimit: optionsLimit,
|
||||
shouldSort: false,
|
||||
})
|
||||
|
||||
this.$refs.input.addEventListener('change', () => {
|
||||
this.refreshPlaceholder()
|
||||
await this.refreshChoices({ withInitialOptions: true })
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(
|
||||
this.formatState(this.state),
|
||||
)
|
||||
}
|
||||
|
||||
this.isStateBeingUpdated = true
|
||||
this.state = this.select.getValue(true) ?? null
|
||||
this.$nextTick(() => this.isStateBeingUpdated = false)
|
||||
})
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (hasDynamicOptions) {
|
||||
this.$refs.input.addEventListener('showDropdown', async () => {
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices(
|
||||
[{ value: '', label: loadingMessage, disabled: true }],
|
||||
)
|
||||
if (isAutofocused) {
|
||||
this.select.showDropdown()
|
||||
}
|
||||
|
||||
await this.refreshChoices()
|
||||
})
|
||||
}
|
||||
this.$refs.input.addEventListener('change', () => {
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (hasDynamicSearchResults) {
|
||||
this.$refs.input.addEventListener('search', async (event) => {
|
||||
let search = event.detail.value?.trim()
|
||||
|
||||
if ([null, undefined, ''].includes(search)) {
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isSearching = true
|
||||
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices(
|
||||
[{ value: '', label: searchingMessage, disabled: true }],
|
||||
)
|
||||
this.isStateBeingUpdated = true
|
||||
this.state = this.select.getValue(true) ?? null
|
||||
this.$nextTick(() => (this.isStateBeingUpdated = false))
|
||||
})
|
||||
|
||||
this.$refs.input.addEventListener('search', Alpine.debounce(async (event) => {
|
||||
await this.refreshChoices({
|
||||
search: event.detail.value?.trim(),
|
||||
if (hasDynamicOptions) {
|
||||
this.$refs.input.addEventListener(
|
||||
'showDropdown',
|
||||
async () => {
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices([
|
||||
{
|
||||
value: '',
|
||||
label: loadingMessage,
|
||||
disabled: true,
|
||||
},
|
||||
])
|
||||
|
||||
await this.refreshChoices()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
if (hasDynamicSearchResults) {
|
||||
this.$refs.input.addEventListener(
|
||||
'search',
|
||||
async (event) => {
|
||||
let search = event.detail.value?.trim()
|
||||
|
||||
if ([null, undefined, ''].includes(search)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.isSearching = true
|
||||
|
||||
this.select.clearChoices()
|
||||
await this.select.setChoices([
|
||||
{
|
||||
value: '',
|
||||
label: searchingMessage,
|
||||
disabled: true,
|
||||
},
|
||||
])
|
||||
},
|
||||
)
|
||||
|
||||
this.$refs.input.addEventListener(
|
||||
'search',
|
||||
Alpine.debounce(async (event) => {
|
||||
await this.refreshChoices({
|
||||
search: event.detail.value?.trim(),
|
||||
})
|
||||
|
||||
this.isSearching = false
|
||||
}, 1000),
|
||||
)
|
||||
}
|
||||
|
||||
this.$watch('state', async () => {
|
||||
this.refreshPlaceholder()
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
const choices = await this.getChoices({
|
||||
withInitialOptions: !hasDynamicOptions,
|
||||
})
|
||||
|
||||
this.isSearching = false
|
||||
}, 1000))
|
||||
}
|
||||
this.select.clearStore()
|
||||
|
||||
this.$watch('state', async () => {
|
||||
this.refreshPlaceholder()
|
||||
this.setChoices(choices)
|
||||
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
const choices = await this.getChoices({
|
||||
withInitialOptions: ! hasDynamicOptions,
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(
|
||||
this.formatState(this.state),
|
||||
)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
this.select.clearStore()
|
||||
refreshChoices: async function (config = {}) {
|
||||
this.setChoices(await this.getChoices(config))
|
||||
},
|
||||
|
||||
this.setChoices(choices)
|
||||
setChoices: function (choices) {
|
||||
this.select.setChoices(choices, 'value', 'label', true)
|
||||
},
|
||||
|
||||
if (! [null, undefined, ''].includes(this.state)) {
|
||||
this.select.setChoiceByValue(this.formatState(this.state))
|
||||
getChoices: async function (config = {}) {
|
||||
const options = await this.getOptions(config)
|
||||
|
||||
return this.transformOptionsIntoChoices({
|
||||
...options,
|
||||
...(await this.getMissingOptions(options)),
|
||||
})
|
||||
},
|
||||
|
||||
getOptions: async function ({ search, withInitialOptions }) {
|
||||
if (withInitialOptions) {
|
||||
return options
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
refreshChoices: async function (config = {}) {
|
||||
this.setChoices(await this.getChoices(config))
|
||||
},
|
||||
if (
|
||||
search !== '' &&
|
||||
search !== null &&
|
||||
search !== undefined
|
||||
) {
|
||||
return await getSearchResultsUsing(search)
|
||||
}
|
||||
|
||||
setChoices: function (choices) {
|
||||
this.select.setChoices(
|
||||
choices,
|
||||
'value',
|
||||
'label',
|
||||
true,
|
||||
)
|
||||
},
|
||||
return await getOptionsUsing()
|
||||
},
|
||||
|
||||
getChoices: async function (config = {}) {
|
||||
const options = await this.getOptions(config)
|
||||
|
||||
return this.transformOptionsIntoChoices({
|
||||
...options,
|
||||
...await this.getMissingOptions(options),
|
||||
})
|
||||
},
|
||||
|
||||
getOptions: async function ({ search, withInitialOptions }) {
|
||||
if (withInitialOptions) {
|
||||
return options
|
||||
}
|
||||
|
||||
if ((search !== '') && (search !== null) && (search !== undefined)) {
|
||||
return await getSearchResultsUsing(search)
|
||||
}
|
||||
|
||||
return await getOptionsUsing()
|
||||
},
|
||||
|
||||
transformOptionsIntoChoices: function (options) {
|
||||
return Object.entries(options)
|
||||
.map(([value, label]) => ({
|
||||
transformOptionsIntoChoices: function (options) {
|
||||
return Object.entries(options).map(([value, label]) => ({
|
||||
label,
|
||||
value,
|
||||
}))
|
||||
},
|
||||
},
|
||||
|
||||
refreshPlaceholder: function () {
|
||||
if (isMultiple) {
|
||||
return
|
||||
}
|
||||
refreshPlaceholder: function () {
|
||||
if (isMultiple) {
|
||||
return
|
||||
}
|
||||
|
||||
this.select._renderItems()
|
||||
this.select._renderItems()
|
||||
|
||||
if (! [null, undefined, ''].includes(this.state)) {
|
||||
return
|
||||
}
|
||||
if (![null, undefined, ''].includes(this.state)) {
|
||||
return
|
||||
}
|
||||
|
||||
this.$el.querySelector('.choices__list--single').innerHTML = `<div class="choices__placeholder choices__item">${placeholder}</div>`
|
||||
},
|
||||
this.$el.querySelector(
|
||||
'.choices__list--single',
|
||||
).innerHTML = `<div class="choices__placeholder choices__item">${placeholder}</div>`
|
||||
},
|
||||
|
||||
formatState: function (state) {
|
||||
if (isMultiple) {
|
||||
return (state ?? []).map((item) => item?.toString())
|
||||
}
|
||||
formatState: function (state) {
|
||||
if (isMultiple) {
|
||||
return (state ?? []).map((item) => item?.toString())
|
||||
}
|
||||
|
||||
return state?.toString()
|
||||
},
|
||||
return state?.toString()
|
||||
},
|
||||
|
||||
getMissingOptions: async function (options) {
|
||||
if ([null, undefined, '', [], {}].includes(this.state)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (! options.length) {
|
||||
options = {}
|
||||
}
|
||||
|
||||
if (isMultiple) {
|
||||
if (this.state.every((value) => value in options)) {
|
||||
getMissingOptions: async function (options) {
|
||||
if ([null, undefined, '', [], {}].includes(this.state)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return await getOptionLabelsUsing()
|
||||
}
|
||||
if (!options.length) {
|
||||
options = {}
|
||||
}
|
||||
|
||||
if (this.state in options) {
|
||||
return options
|
||||
}
|
||||
if (isMultiple) {
|
||||
if (this.state.every((value) => value in options)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
let missingOptions = {}
|
||||
missingOptions[this.state] = await getOptionLabelUsing()
|
||||
return missingOptions
|
||||
},
|
||||
}
|
||||
})
|
||||
return await getOptionLabelsUsing()
|
||||
}
|
||||
|
||||
if (this.state in options) {
|
||||
return options
|
||||
}
|
||||
|
||||
let missingOptions = {}
|
||||
missingOptions[this.state] = await getOptionLabelUsing()
|
||||
return missingOptions
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import '../../css/components/tags-input.css'
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('tagsInputFormComponent', ({
|
||||
state,
|
||||
}) => {
|
||||
Alpine.data('tagsInputFormComponent', ({ state }) => {
|
||||
return {
|
||||
newTag: '',
|
||||
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
import IMask from 'imask'
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.data('textInputFormComponent', ({
|
||||
getMaskOptionsUsing,
|
||||
state,
|
||||
}) => {
|
||||
Alpine.data('textInputFormComponent', ({ getMaskOptionsUsing, state }) => {
|
||||
return {
|
||||
isStateBeingUpdated: false,
|
||||
|
||||
mask: null,
|
||||
|
||||
state,
|
||||
|
||||
init: function () {
|
||||
if (! getMaskOptionsUsing) {
|
||||
if (!getMaskOptionsUsing) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -19,14 +18,25 @@ export default (Alpine) => {
|
||||
this.$el.value = this.state?.valueOf()
|
||||
}
|
||||
|
||||
this.mask = IMask(this.$el, getMaskOptionsUsing(IMask)).on('accept', () => {
|
||||
this.state = this.mask.unmaskedValue
|
||||
})
|
||||
this.mask = IMask(this.$el, getMaskOptionsUsing(IMask)).on(
|
||||
'accept',
|
||||
() => {
|
||||
this.isStateBeingUpdated = true
|
||||
|
||||
this.state = this.mask.unmaskedValue
|
||||
|
||||
this.$nextTick(() => (this.isStateBeingUpdated = false))
|
||||
},
|
||||
)
|
||||
|
||||
this.$watch('state', () => {
|
||||
if (this.isStateBeingUpdated) {
|
||||
return
|
||||
}
|
||||
|
||||
this.mask.unmaskedValue = this.state?.valueOf() ?? ''
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export default (Alpine) => {
|
||||
render: function () {
|
||||
if (this.$el.scrollHeight > 0) {
|
||||
this.$el.style.height = '150px'
|
||||
this.$el.style.height = (this.$el.scrollHeight + 2) + 'px'
|
||||
this.$el.style.height = this.$el.scrollHeight + 2 + 'px'
|
||||
}
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import './sortable'
|
||||
import ColorPickerFormComponentAlpinePlugin from './components/color-picker'
|
||||
import DateTimePickerFormComponentAlpinePlugin from './components/date-time-picker'
|
||||
import FileUploadFormComponentAlpinePlugin from './components/file-upload'
|
||||
@@ -7,8 +6,10 @@ import MarkdownEditorFormComponentAlpinePlugin from './components/markdown-edito
|
||||
import RichEditorFormComponentAlpinePlugin from './components/rich-editor'
|
||||
import SelectFormComponentAlpinePlugin from './components/select'
|
||||
import TagsInputFormComponentAlpinePlugin from './components/tags-input'
|
||||
import TextareaFormComponentAlpinePlugin from './components/textarea'
|
||||
import TextInputFormComponentAlpinePlugin from './components/text-input'
|
||||
import TextareaFormComponentAlpinePlugin from './components/textarea'
|
||||
import SortableAlpinePlugin from './sortable'
|
||||
import AlpineFloatingUI from '@awcodes/alpine-floating-ui'
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.plugin(ColorPickerFormComponentAlpinePlugin)
|
||||
@@ -19,8 +20,10 @@ export default (Alpine) => {
|
||||
Alpine.plugin(RichEditorFormComponentAlpinePlugin)
|
||||
Alpine.plugin(SelectFormComponentAlpinePlugin)
|
||||
Alpine.plugin(TagsInputFormComponentAlpinePlugin)
|
||||
Alpine.plugin(TextareaFormComponentAlpinePlugin)
|
||||
Alpine.plugin(TextInputFormComponentAlpinePlugin)
|
||||
Alpine.plugin(TextareaFormComponentAlpinePlugin)
|
||||
Alpine.plugin(SortableAlpinePlugin)
|
||||
Alpine.plugin(AlpineFloatingUI)
|
||||
}
|
||||
|
||||
export {
|
||||
@@ -32,6 +35,8 @@ export {
|
||||
RichEditorFormComponentAlpinePlugin,
|
||||
SelectFormComponentAlpinePlugin,
|
||||
TagsInputFormComponentAlpinePlugin,
|
||||
TextareaFormComponentAlpinePlugin,
|
||||
TextInputFormComponentAlpinePlugin,
|
||||
TextareaFormComponentAlpinePlugin,
|
||||
SortableAlpinePlugin,
|
||||
AlpineFloatingUI,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import Sortable from 'sortablejs';
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
window.Sortable = Sortable;
|
||||
window.Sortable = Sortable
|
||||
|
||||
window.Livewire.directive('sortable', (el) => {
|
||||
el.sortable = Sortable.create(el, {
|
||||
draggable: '[wire\\:sortable\\.item]',
|
||||
handle: '[wire\\:sortable\\.handle]',
|
||||
dataIdAttr: 'wire:sortable.item',
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
||||
export default (Alpine) => {
|
||||
Alpine.directive('sortable', (el) => {
|
||||
el.sortable = Sortable.create(el, {
|
||||
draggable: '[x-sortable-item]',
|
||||
handle: '[x-sortable-handle]',
|
||||
dataIdAttr: 'x-sortable-item',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
return [
|
||||
|
||||
'file_upload' => [
|
||||
'filepond_locale' => 'da-dk',
|
||||
],
|
||||
|
||||
'rich_editor' => [
|
||||
|
||||
'dialogs' => [
|
||||
|
||||
@@ -103,6 +103,10 @@ return [
|
||||
'label' => 'Borrar',
|
||||
],
|
||||
|
||||
'clone_item' => [
|
||||
'label' => 'Clonar',
|
||||
],
|
||||
|
||||
'move_item_down' => [
|
||||
'label' => 'Bajar',
|
||||
],
|
||||
|
||||
@@ -2,10 +2,6 @@
|
||||
|
||||
return [
|
||||
|
||||
'file_upload' => [
|
||||
'filepond_locale' => 'fa_ir',
|
||||
],
|
||||
|
||||
'builder' => [
|
||||
|
||||
'collapsed' => 'محتوا جمع شد',
|
||||
|
||||
@@ -4,16 +4,14 @@ return [
|
||||
|
||||
'builder' => [
|
||||
|
||||
'collapsed' => 'Kandungan ditutup',
|
||||
|
||||
'buttons' => [
|
||||
|
||||
'create_item' => [
|
||||
'label' => 'Tambah ke :label',
|
||||
],
|
||||
|
||||
'create_item_between' => [
|
||||
'label' => 'Tambah antara',
|
||||
],
|
||||
|
||||
'delete_item' => [
|
||||
'label' => 'Padam',
|
||||
],
|
||||
@@ -26,6 +24,22 @@ return [
|
||||
'label' => 'Pindah ke atas',
|
||||
],
|
||||
|
||||
'collapse_item' => [
|
||||
'label' => 'Tutup',
|
||||
],
|
||||
|
||||
'expand_item' => [
|
||||
'label' => 'Buka',
|
||||
],
|
||||
|
||||
'collapse_all' => [
|
||||
'label' => 'Tutup semua',
|
||||
],
|
||||
|
||||
'expand_all' => [
|
||||
'label' => 'Buka semua',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
@@ -77,6 +91,8 @@ return [
|
||||
|
||||
'repeater' => [
|
||||
|
||||
'collapsed' => 'Kandungan ditutup',
|
||||
|
||||
'buttons' => [
|
||||
|
||||
'create_item' => [
|
||||
@@ -87,6 +103,10 @@ return [
|
||||
'label' => 'Padam',
|
||||
],
|
||||
|
||||
'clone_item' => [
|
||||
'label' => 'Klon',
|
||||
],
|
||||
|
||||
'move_item_down' => [
|
||||
'label' => 'Pindah ke atas',
|
||||
],
|
||||
@@ -95,6 +115,22 @@ return [
|
||||
'label' => 'Pindah ke bawah',
|
||||
],
|
||||
|
||||
'collapse_item' => [
|
||||
'label' => 'Tutup',
|
||||
],
|
||||
|
||||
'expand_item' => [
|
||||
'label' => 'Buka',
|
||||
],
|
||||
|
||||
'collapse_all' => [
|
||||
'label' => 'Tutup semua',
|
||||
],
|
||||
|
||||
'expand_all' => [
|
||||
'label' => 'Buka semua',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
@@ -138,13 +174,64 @@ return [
|
||||
],
|
||||
|
||||
'select' => [
|
||||
|
||||
'actions' => [
|
||||
|
||||
'create_option' => [
|
||||
|
||||
'modal' => [
|
||||
|
||||
'heading' => 'Cipta',
|
||||
|
||||
'actions' => [
|
||||
|
||||
'create' => [
|
||||
'label' => 'Cipta',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
'boolean' => [
|
||||
'true' => 'Ya',
|
||||
'false' => 'Tidak',
|
||||
],
|
||||
|
||||
'loading_message' => 'Memuatkan...',
|
||||
|
||||
'no_search_results_message' => 'Tiada pilihan yang sepadan dengan carian anda.',
|
||||
|
||||
'placeholder' => 'Pilih satu pilihan',
|
||||
|
||||
'searching_message' => 'Mencari...',
|
||||
|
||||
'search_prompt' => 'Mula menaip untuk mencari...',
|
||||
|
||||
],
|
||||
|
||||
'tags_input' => [
|
||||
'placeholder' => 'Tag baru',
|
||||
],
|
||||
|
||||
'wizard' => [
|
||||
|
||||
'buttons' => [
|
||||
|
||||
'previous_step' => [
|
||||
'label' => 'Sebelum',
|
||||
],
|
||||
|
||||
'next_step' => [
|
||||
'label' => 'Seterus',
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -61,7 +61,7 @@ return [
|
||||
'fields' => [
|
||||
|
||||
'key' => [
|
||||
'label' => 'Key',
|
||||
'label' => 'Khóa',
|
||||
],
|
||||
|
||||
'value' => [
|
||||
@@ -79,7 +79,7 @@ return [
|
||||
'bold' => 'In đậm',
|
||||
'bullet_list' => 'Danh sách dùng dấu chấm',
|
||||
'code_block' => 'Code block',
|
||||
'edit' => 'Xóa',
|
||||
'edit' => 'Sửa',
|
||||
'italic' => 'In nghiêng',
|
||||
'link' => 'Liên kết',
|
||||
'ordered_list' => 'Danh sách đánh số',
|
||||
@@ -151,7 +151,7 @@ return [
|
||||
],
|
||||
|
||||
'toolbar_buttons' => [
|
||||
'attach_files' => 'Đính kèm file',
|
||||
'attach_files' => 'Đính kèm tệp',
|
||||
'blockquote' => 'Trích dẫn',
|
||||
'bold' => 'In đậm',
|
||||
'bullet_list' => 'Danh sách dùng dấu chấm',
|
||||
@@ -162,8 +162,8 @@ return [
|
||||
'italic' => 'In nghiêng',
|
||||
'link' => 'Liên kết',
|
||||
'ordered_list' => 'Danh sách đánh số',
|
||||
'redo' => 'Redo',
|
||||
'strike' => 'Strikethrough',
|
||||
'redo' => 'Làm lại',
|
||||
'strike' => 'Gạch ngang',
|
||||
'undo' => 'Hoàn tác',
|
||||
],
|
||||
|
||||
|
||||
+3
@@ -15,7 +15,10 @@
|
||||
<x-forms::button
|
||||
:form="$getForm()"
|
||||
:type="$canSubmitForm() ? 'submit' : 'button'"
|
||||
:tag="$action->getUrl() ? 'a' : 'button'"
|
||||
:wire:click="$wireClickAction"
|
||||
:href="$action->isEnabled() ? $action->getUrl() : null"
|
||||
:target="$action->shouldOpenUrlInNewTab() ? '_blank' : null"
|
||||
:x-on:click="$canCancelAction() ? 'isOpen = false' : null"
|
||||
:color="$getColor()"
|
||||
:outlined="$isOutlined()"
|
||||
|
||||
@@ -3,7 +3,22 @@
|
||||
@endphp
|
||||
|
||||
<form wire:submit.prevent="callMountedFormComponentAction">
|
||||
<x-forms::modal :id="\Illuminate\Support\Str::of(static::class)->replace('\\', '\\\\') . '-form-component-action'" :visible="filled($action)" :width="$action?->getModalWidth()" display-classes="block">
|
||||
<x-forms::modal
|
||||
:id="$this->id . '-form-component-action'"
|
||||
:wire:key="$action ? $this->id . '.' . $action->getComponent()->getStatePath() . '.actions.' . $action->getName() . '.modal' : null"
|
||||
x-init="
|
||||
$watch('isOpen', () => {
|
||||
if (isOpen) {
|
||||
return
|
||||
}
|
||||
|
||||
$wire.mountedFormComponentAction = null
|
||||
})
|
||||
"
|
||||
:visible="filled($action)"
|
||||
:width="$action?->getModalWidth()"
|
||||
display-classes="block"
|
||||
>
|
||||
@if ($action)
|
||||
@if ($action->isModalCentered())
|
||||
<x-slot name="heading">
|
||||
@@ -20,6 +35,12 @@
|
||||
<x-forms::modal.heading>
|
||||
{{ $action->getModalHeading() }}
|
||||
</x-forms::modal.heading>
|
||||
|
||||
@if ($subheading = $action->getModalSubheading())
|
||||
<x-forms::modal.subheading>
|
||||
{{ $subheading }}
|
||||
</x-forms::modal.subheading>
|
||||
@endif
|
||||
</x-slot>
|
||||
@endif
|
||||
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
@once
|
||||
@push('scripts')
|
||||
@php
|
||||
$locale = strtolower(str_replace('_', '-', app()->getLocale()));
|
||||
@endphp
|
||||
|
||||
<script defer src="//unpkg.com/dayjs@1.10.4/locale/{{ $locale }}.js" onload="dayjs.updateLocale('{{ $locale }}')"></script>
|
||||
@endpush
|
||||
@endonce
|
||||
|
||||
<x-dynamic-component
|
||||
:component="$getFieldWrapperView()"
|
||||
:id="$getId()"
|
||||
@@ -25,6 +15,7 @@
|
||||
firstDayOfWeek: {{ $getFirstDayOfWeek() }},
|
||||
isAutofocused: @js($isAutofocused()),
|
||||
isDisabled: @js($isDisabled()),
|
||||
locale: @js(app()->getLocale()),
|
||||
shouldCloseOnDateSelection: @js($shouldCloseOnDateSelection()),
|
||||
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')') }},
|
||||
})"
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
@once
|
||||
@push('scripts')
|
||||
@php
|
||||
if (\Illuminate\Support\Facades\Lang::has($localeString = 'forms::components.file_upload.filepond_locale')) {
|
||||
$locale = __($localeString);
|
||||
} else {
|
||||
$locale = strtolower(str_replace('_', '-', app()->getLocale()));
|
||||
|
||||
if (! str_contains($locale, '-')) {
|
||||
$locale .= '-' . $locale;
|
||||
}
|
||||
}
|
||||
|
||||
$defaultLocaleData = ($placeholder = $getPlaceholder()) ? "{ labelIdle: '{$placeholder}' }" : '{}';
|
||||
@endphp
|
||||
|
||||
<script type="module">
|
||||
import localeData from 'https://cdn.skypack.dev/filepond/locale/{{$locale}}.js'
|
||||
|
||||
window.FilePond && window.FilePond.setOptions({ ...localeData, ...{!! $defaultLocaleData !!} })
|
||||
</script>
|
||||
@endpush
|
||||
@endonce
|
||||
|
||||
<x-dynamic-component
|
||||
:component="$getFieldWrapperView()"
|
||||
:id="$getId()"
|
||||
@@ -60,9 +36,10 @@
|
||||
imageResizeTargetWidth: {{ $imageResizeTargetWidth ? "'{$imageResizeTargetWidth}'" : 'null' }},
|
||||
isAvatar: {{ $isAvatar() ? 'true' : 'false' }},
|
||||
loadingIndicatorPosition: '{{ $getLoadingIndicatorPosition() }}',
|
||||
locale: @js(app()->getLocale()),
|
||||
panelAspectRatio: {{ ($aspectRatio = $getPanelAspectRatio()) ? "'{$aspectRatio}'" : 'null' }},
|
||||
panelLayout: {{ ($layout = $getPanelLayout()) ? "'{$layout}'" : 'null' }},
|
||||
placeholder: {{ ($placeholder = $getPlaceholder()) ? "'{$placeholder}'" : 'null' }},
|
||||
placeholder: @js($getPlaceholder()),
|
||||
maxSize: {{ ($size = $getMaxSize()) ? "'{$size} KB'" : 'null' }},
|
||||
minSize: {{ ($size = $getMinSize()) ? "'{$size} KB'" : 'null' }},
|
||||
removeUploadedFileUsing: async (fileKey) => {
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
{{ $getChildComponentContainer() }}
|
||||
<div {{ $attributes->merge($getExtraAttributes()) }}>
|
||||
{{ $getChildComponentContainer() }}
|
||||
</div>
|
||||
|
||||
@@ -1 +1,3 @@
|
||||
{{ $getChildComponentContainer() }}
|
||||
<div {{ $attributes->merge($getExtraAttributes()) }}>
|
||||
{{ $getChildComponentContainer() }}
|
||||
</div>
|
||||
|
||||
@@ -43,17 +43,23 @@
|
||||
{{ $getValueLabel() }}
|
||||
</th>
|
||||
|
||||
@if ($canDeleteRows() && $getDeleteButtonLabel() && $isEnabled())
|
||||
<th class="w-12" scope="col" x-show="rows.length > 1">
|
||||
<span class="sr-only">
|
||||
{{ $getDeleteButtonLabel() }}
|
||||
</span>
|
||||
@if (($canDeleteRows() || $isReorderable()) && $isEnabled())
|
||||
<th
|
||||
scope="col"
|
||||
x-show="rows.length > 1"
|
||||
class="{{ ($canDeleteRows() && $isReorderable()) ? 'w-16' : 'w-12' }}"
|
||||
>
|
||||
<span class="sr-only"></span>
|
||||
</th>
|
||||
@endif
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody
|
||||
@if ($isReorderable())
|
||||
x-sortable
|
||||
x-on:end="reorderRows($event)"
|
||||
@endif
|
||||
x-ref="tableBody"
|
||||
@class([
|
||||
'divide-y whitespace-nowrap',
|
||||
@@ -61,10 +67,15 @@
|
||||
])
|
||||
>
|
||||
<template x-for="(row, index) in rows" x-bind:key="index" x-ref="rowTemplate">
|
||||
<tr @class([
|
||||
'divide-x',
|
||||
'dark:divide-gray-600' => config('forms.dark_mode'),
|
||||
])>
|
||||
<tr
|
||||
@if ($isReorderable())
|
||||
x-bind:x-sortable-item="row.key"
|
||||
@endif
|
||||
@class([
|
||||
'divide-x',
|
||||
'dark:divide-gray-600' => config('forms.dark_mode'),
|
||||
])
|
||||
>
|
||||
<td>
|
||||
<input
|
||||
type="text"
|
||||
@@ -91,16 +102,36 @@
|
||||
>
|
||||
</td>
|
||||
|
||||
@if ($canDeleteRows() && $isEnabled())
|
||||
@if (($canDeleteRows() || $isReorderable()) && $isEnabled())
|
||||
<td x-show="rows.length > 1" class="whitespace-nowrap">
|
||||
<div class="flex items-center justify-center">
|
||||
<button
|
||||
x-on:click="deleteRow(index)"
|
||||
type="button"
|
||||
class="text-danger-600 hover:text-danger-700"
|
||||
>
|
||||
<x-heroicon-o-trash class="w-4 h-4" />
|
||||
</button>
|
||||
<div class="flex items-center justify-center gap-2">
|
||||
@if ($isReorderable())
|
||||
<button
|
||||
x-sortable-handle
|
||||
type="button"
|
||||
class="text-gray-600 hover:text-gray-700"
|
||||
>
|
||||
<x-heroicon-o-switch-vertical class="w-4 h-4" />
|
||||
|
||||
<span class="sr-only">
|
||||
{{ $getDeleteButtonLabel() }}
|
||||
</span>
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if ($canDeleteRows())
|
||||
<button
|
||||
x-on:click="deleteRow(index)"
|
||||
type="button"
|
||||
class="text-danger-600 hover:text-danger-700"
|
||||
>
|
||||
<x-heroicon-o-trash class="w-4 h-4" />
|
||||
|
||||
<span class="sr-only">
|
||||
{{ $getDeleteButtonLabel() }}
|
||||
</span>
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
</td>
|
||||
@endif
|
||||
|
||||
@@ -217,7 +217,7 @@
|
||||
{!! $isRequired() ? 'required' : null !!}
|
||||
@endif
|
||||
@class([
|
||||
'tracking-normal whitespace-pre-wrap overflow-y-hidden font-mono block absolute bg-transparent top-0 text-sm left-0 block z-1 w-full h-full min-h-full resize-none transition duration-75 rounded-lg shadow-sm focus:border-primary-600 focus:ring-1 focus:ring-inset focus:ring-primary-600 caret-black',
|
||||
'tracking-normal overflow-y-hidden font-mono block absolute bg-transparent top-0 text-sm left-0 block z-1 w-full h-full min-h-full resize-none transition duration-75 rounded-lg shadow-sm focus:border-primary-600 focus:ring-1 focus:ring-inset focus:ring-primary-600 caret-black',
|
||||
'dark:caret-white dark:focus:border-primary-600' => config('forms.dark_mode'),
|
||||
'border-gray-300' => ! $errors->has($getStatePath()),
|
||||
'dark:border-gray-600' => (! $errors->has($getStatePath())) && config('forms.dark_mode'),
|
||||
@@ -231,14 +231,14 @@
|
||||
x-html="overlay"
|
||||
style="min-height: 150px;"
|
||||
@class([
|
||||
'w-full h-full rounded-lg px-3 py-2 border border-transparent font-mono tracking-normal bg-white text-sm text-gray-900 break-words whitespace-pre-wrap',
|
||||
'w-full h-full rounded-lg px-3 py-2 border border-transparent font-mono tracking-normal bg-white text-sm text-gray-900 break-words',
|
||||
'dark:bg-gray-700 dark:border-gray-600 dark:text-white' => config('forms.dark_mode'),
|
||||
])
|
||||
></div>
|
||||
</div>
|
||||
|
||||
<div @class([
|
||||
'prose max-w-none block w-full h-full min-h-full px-3 py-2 bg-white border border-gray-300 rounded-lg shadow-sm focus:border-primary-300',
|
||||
'prose max-w-none block w-full h-full min-h-full px-3 py-2 bg-white border border-gray-300 rounded-lg shadow-sm break-words focus:border-primary-300',
|
||||
'dark:prose-invert dark:border-gray-600 dark:bg-gray-700' => config('forms.dark_mode'),
|
||||
]) x-show="tab === 'preview'" x-html="preview" x-cloak style="min-height: 150px;"></div>
|
||||
</div>
|
||||
|
||||
@@ -40,6 +40,15 @@
|
||||
x-on:keydown.enter.stop.prevent="createTag()"
|
||||
x-on:keydown.,.stop.prevent="createTag()"
|
||||
x-on:blur="createTag()"
|
||||
x-on:paste="$nextTick(() => {
|
||||
if (newTag.includes(',')) {
|
||||
newTag.split(',').forEach((tag) => {
|
||||
newTag = tag
|
||||
|
||||
createTag()
|
||||
})
|
||||
}
|
||||
})"
|
||||
x-model="newTag"
|
||||
{{ $getExtraInputAttributeBag()->class([
|
||||
'webkit-calendar-picker-indicator:opacity-0 block w-full border-0',
|
||||
|
||||
@@ -42,11 +42,7 @@
|
||||
@else
|
||||
x-data="textInputFormComponent({
|
||||
{{ $hasMask() ? "getMaskOptionsUsing: (IMask) => ({$getJsonMaskConfiguration()})," : null }}
|
||||
state: $wire.{{
|
||||
$isLazy() ?
|
||||
'entangle(\'' . $getStatePath() . '\').defer' :
|
||||
$applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')')
|
||||
}},
|
||||
state: $wire.{{ $applyStateBindingModifiers('entangle(\'' . $getStatePath() . '\')') }},
|
||||
})"
|
||||
type="text"
|
||||
wire:ignore
|
||||
|
||||
@@ -19,8 +19,20 @@
|
||||
x-bind:aria-checked="state.toString()"
|
||||
x-on:click="state = ! state"
|
||||
x-bind:class="{
|
||||
'bg-primary-600': state,
|
||||
'bg-gray-200 @if (config('forms.dark_mode')) dark:bg-white/10 @endif': ! state,
|
||||
'{{ match ($getOnColor()) {
|
||||
'danger' => 'bg-danger-500',
|
||||
'secondary' => 'bg-gray-500',
|
||||
'success' => 'bg-success-500',
|
||||
'warning' => 'bg-warning-500',
|
||||
default => 'bg-primary-600',
|
||||
} }}': state,
|
||||
'{{ match ($getOffColor()) {
|
||||
'danger' => 'bg-danger-500',
|
||||
'primary' => 'bg-primary-500',
|
||||
'success' => 'bg-success-500',
|
||||
'warning' => 'bg-warning-500',
|
||||
default => 'bg-gray-200',
|
||||
} }} @if (config('forms.dark_mode')) dark:bg-white/10 @endif': ! state,
|
||||
}"
|
||||
{!! $isAutofocused() ? 'autofocus' : null !!}
|
||||
{!! $isDisabled() ? 'disabled' : null !!}
|
||||
|
||||
@@ -41,6 +41,8 @@ class BaseFileUpload extends Field
|
||||
|
||||
protected bool | Closure $shouldPreserveFilenames = false;
|
||||
|
||||
protected string | Closure | null $fileNamesStatePath = null;
|
||||
|
||||
protected string | Closure $visibility = 'public';
|
||||
|
||||
protected ?Closure $deleteUploadedFileUsing = null;
|
||||
@@ -73,6 +75,10 @@ class BaseFileUpload extends Field
|
||||
});
|
||||
|
||||
$this->afterStateUpdated(static function (BaseFileUpload $component, $state) {
|
||||
if ($state instanceof TemporaryUploadedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (blank($state)) {
|
||||
return;
|
||||
}
|
||||
@@ -203,6 +209,13 @@ class BaseFileUpload extends Field
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function storeFileNamesIn(string | Closure | null $statePath): static
|
||||
{
|
||||
$this->fileNamesStatePath = $statePath;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function preserveFilenames(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->shouldPreserveFilenames = $condition;
|
||||
@@ -358,6 +371,15 @@ class BaseFileUpload extends Field
|
||||
return $this->evaluate($this->shouldPreserveFilenames);
|
||||
}
|
||||
|
||||
public function getFileNamesStatePath(): ?string
|
||||
{
|
||||
if (! $this->fileNamesStatePath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->generateRelativeStatePath($this->fileNamesStatePath);
|
||||
}
|
||||
|
||||
public function getValidationRules(): array
|
||||
{
|
||||
$rules = [
|
||||
@@ -424,7 +446,9 @@ class BaseFileUpload extends Field
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($file instanceof TemporaryUploadedFile) {
|
||||
if (is_string($file)) {
|
||||
$this->removeStoredFileName($file);
|
||||
} elseif ($file instanceof TemporaryUploadedFile) {
|
||||
$file->delete();
|
||||
}
|
||||
|
||||
@@ -435,6 +459,31 @@ class BaseFileUpload extends Field
|
||||
return $file;
|
||||
}
|
||||
|
||||
public function removeStoredFileName(string $file): void
|
||||
{
|
||||
$statePath = $this->fileNamesStatePath;
|
||||
|
||||
if (blank($statePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->evaluate(function (BaseFileUpload $component, Closure $get, Closure $set) use ($file, $statePath) {
|
||||
if (! $component->isMultiple()) {
|
||||
$set($statePath, null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fileNames = $get($statePath) ?? [];
|
||||
|
||||
if (array_key_exists($file, $fileNames)) {
|
||||
unset($fileNames[$file]);
|
||||
}
|
||||
|
||||
$set($statePath, $fileNames);
|
||||
});
|
||||
}
|
||||
|
||||
public function reorderUploadedFiles(array $fileKeys): void
|
||||
{
|
||||
if (! $this->canReorder) {
|
||||
@@ -483,10 +532,6 @@ class BaseFileUpload extends Field
|
||||
return;
|
||||
}
|
||||
|
||||
if (! is_array($this->getState())) {
|
||||
$this->state([$this->getState()]);
|
||||
}
|
||||
|
||||
$state = array_map(function (TemporaryUploadedFile | string $file) {
|
||||
if (! $file instanceof TemporaryUploadedFile) {
|
||||
return $file;
|
||||
@@ -504,10 +549,12 @@ class BaseFileUpload extends Field
|
||||
'file' => $file,
|
||||
]);
|
||||
|
||||
$this->storeFileName($storedFile, $file->getClientOriginalName());
|
||||
|
||||
$file->delete();
|
||||
|
||||
return $storedFile;
|
||||
}, $this->getState());
|
||||
}, Arr::wrap($this->getState()));
|
||||
|
||||
if ($this->canReorder && ($callback = $this->reorderUploadedFilesUsing)) {
|
||||
$state = $this->evaluate($callback, [
|
||||
@@ -518,6 +565,44 @@ class BaseFileUpload extends Field
|
||||
$this->state($state);
|
||||
}
|
||||
|
||||
public function storeFileName(string $file, string $fileName): void
|
||||
{
|
||||
$statePath = $this->fileNamesStatePath;
|
||||
|
||||
if (blank($statePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->evaluate(function (BaseFileUpload $component, Closure $get, Closure $set) use ($file, $fileName, $statePath) {
|
||||
if (! $component->isMultiple()) {
|
||||
$set($statePath, $fileName);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$fileNames = $get($statePath) ?? [];
|
||||
$fileNames[$file] = $fileName;
|
||||
|
||||
$set($statePath, $fileNames);
|
||||
});
|
||||
}
|
||||
|
||||
public function getStoredFileNames(): string | array | null
|
||||
{
|
||||
$state = null;
|
||||
$statePath = $this->fileNamesStatePath;
|
||||
|
||||
if (filled($statePath)) {
|
||||
$state = $this->evaluate(fn (Closure $get) => $get($statePath));
|
||||
}
|
||||
|
||||
if (blank($state) && $this->isMultiple()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
public function isMultiple(): bool
|
||||
{
|
||||
return $this->evaluate($this->isMultiple);
|
||||
@@ -536,4 +621,26 @@ class BaseFileUpload extends Field
|
||||
'file' => $file,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getStateToDehydrate(): array
|
||||
{
|
||||
$state = parent::getStateToDehydrate();
|
||||
|
||||
if ($fileNamesStatePath = $this->getFileNamesStatePath()) {
|
||||
$state = array_merge($state, [
|
||||
$fileNamesStatePath => $this->getStoredFileNames(),
|
||||
]);
|
||||
}
|
||||
|
||||
return $state;
|
||||
}
|
||||
|
||||
public function dehydrateValidationRules(array &$rules): void
|
||||
{
|
||||
parent::dehydrateValidationRules($rules);
|
||||
|
||||
if ($fileNamesStatePath = $this->getFileNamesStatePath()) {
|
||||
$rules[$fileNamesStatePath] = ['nullable'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ trait BelongsToModel
|
||||
$this->evaluate($callback);
|
||||
}
|
||||
|
||||
public function loadStateFromRelationships(): void
|
||||
public function loadStateFromRelationships(bool $andHydrate = false): void
|
||||
{
|
||||
$callback = $this->loadStateFromRelationshipsUsing;
|
||||
|
||||
@@ -39,7 +39,21 @@ trait BelongsToModel
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->getRecord()?->exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->evaluate($callback);
|
||||
|
||||
if ($andHydrate) {
|
||||
$this->callAfterStateHydrated();
|
||||
|
||||
foreach ($this->getChildComponentContainers() as $container) {
|
||||
$container->callAfterStateHydrated();
|
||||
}
|
||||
|
||||
$this->fillStateWithNull();
|
||||
}
|
||||
}
|
||||
|
||||
public function saveRelationshipsUsing(?Closure $callback): static
|
||||
|
||||
@@ -28,7 +28,7 @@ trait CanBeLengthConstrained
|
||||
}
|
||||
|
||||
return "size:{$length}";
|
||||
});
|
||||
}, static fn (Contracts\CanBeLengthConstrained $component): bool => filled($component->getLength()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -40,8 +40,12 @@ trait CanBeLengthConstrained
|
||||
$this->rule(static function (Contracts\CanBeLengthConstrained $component): string {
|
||||
$length = $component->getMaxLength();
|
||||
|
||||
if ($component instanceof Contracts\CanHaveNumericState && $component->isNumeric()) {
|
||||
return "max_digits:{$length}";
|
||||
}
|
||||
|
||||
return "max:{$length}";
|
||||
});
|
||||
}, static fn (Contracts\CanBeLengthConstrained $component): bool => filled($component->getMaxLength()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -53,8 +57,12 @@ trait CanBeLengthConstrained
|
||||
$this->rule(static function (Contracts\CanBeLengthConstrained $component): string {
|
||||
$length = $component->getMinLength();
|
||||
|
||||
if ($component instanceof Contracts\CanHaveNumericState && $component->isNumeric()) {
|
||||
return "min_digits:{$length}";
|
||||
}
|
||||
|
||||
return "min:{$length}";
|
||||
});
|
||||
}, static fn (Contracts\CanBeLengthConstrained $component): bool => filled($component->getMinLength()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -208,6 +208,18 @@ trait CanBeValidated
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function dehydrateValidationRules(array &$rules): void
|
||||
{
|
||||
if (count($componentRules = $this->getValidationRules())) {
|
||||
$rules[$this->getStatePath()] = $componentRules;
|
||||
}
|
||||
}
|
||||
|
||||
public function dehydrateValidationAttributes(array &$attributes): void
|
||||
{
|
||||
$attributes[$this->getStatePath()] = $this->getValidationAttribute();
|
||||
}
|
||||
|
||||
public function isRequired(): bool
|
||||
{
|
||||
return (bool) $this->evaluate($this->isRequired);
|
||||
|
||||
@@ -55,6 +55,7 @@ trait EntanglesStateWithSingularRelationship
|
||||
|
||||
if ($relationship instanceof BelongsTo) {
|
||||
$relationship->associate($record->create($state));
|
||||
$relationship->getParent()->save();
|
||||
} else {
|
||||
$record->fill($state);
|
||||
$relationship->save($record);
|
||||
@@ -83,7 +84,7 @@ trait EntanglesStateWithSingularRelationship
|
||||
|
||||
protected function getStateFromRelatedRecord(Model $record): array
|
||||
{
|
||||
$state = $record->toArray();
|
||||
$state = $record->attributesToArray();
|
||||
|
||||
if (
|
||||
($activeLocale = $this->getLivewire()->getActiveFormLocale()) &&
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace Filament\Forms\Components\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Filament\Forms\Components\Component;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Livewire;
|
||||
|
||||
@@ -92,13 +94,36 @@ trait HasState
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function dehydrateState()
|
||||
public function getStateToDehydrate(): array
|
||||
{
|
||||
if ($callback = $this->dehydrateStateUsing) {
|
||||
return $this->evaluate($callback);
|
||||
return [$this->getStatePath() => $this->evaluate($callback)];
|
||||
}
|
||||
|
||||
return $this->getState();
|
||||
return [$this->getStatePath() => $this->getState()];
|
||||
}
|
||||
|
||||
public function dehydrateState(array &$state): void
|
||||
{
|
||||
if (! $this->isDehydrated()) {
|
||||
Arr::forget($state, $this->getStatePath());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->getStatePath(isAbsolute: false)) {
|
||||
foreach ($this->getStateToDehydrate() as $key => $value) {
|
||||
Arr::set($state, $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->getChildComponentContainers() as $container) {
|
||||
if ($container->isHidden()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$container->dehydrateState($state);
|
||||
}
|
||||
}
|
||||
|
||||
public function dehydrateStateUsing(?Closure $callback): static
|
||||
@@ -108,29 +133,68 @@ trait HasState
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function hydrateDefaultState(): static
|
||||
public function hydrateState(?array &$hydratedDefaultState): void
|
||||
{
|
||||
if (! $this->hasDefaultState()) {
|
||||
return $this;
|
||||
$this->hydrateDefaultState($hydratedDefaultState);
|
||||
|
||||
foreach ($this->getChildComponentContainers(withHidden: true) as $container) {
|
||||
$container->hydrateState($hydratedDefaultState);
|
||||
}
|
||||
|
||||
$this->callAfterStateHydrated();
|
||||
}
|
||||
|
||||
public function fill(): void
|
||||
{
|
||||
$defaults = [];
|
||||
|
||||
$this->hydrateDefaultState($defaults);
|
||||
}
|
||||
|
||||
public function hydrateDefaultState(?array &$hydratedDefaultState): void
|
||||
{
|
||||
if ($hydratedDefaultState === null) {
|
||||
$this->loadStateFromRelationships();
|
||||
|
||||
$state = $this->getState();
|
||||
|
||||
// Hydrate all arrayable state objects as arrays by converting
|
||||
// them to collections, then using `toArray()`.
|
||||
if (is_array($state) || $state instanceof Arrayable) {
|
||||
$this->state(collect($state)->toArray());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$statePath = $this->getStatePath();
|
||||
|
||||
if (Arr::has($hydratedDefaultState, $statePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->hasDefaultState()) {
|
||||
$this->state(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$defaultState = $this->getDefaultState();
|
||||
|
||||
$this->state($this->getDefaultState());
|
||||
|
||||
return $this;
|
||||
Arr::set($hydratedDefaultState, $statePath, $defaultState);
|
||||
}
|
||||
|
||||
public function fillStateWithNull(bool $shouldOverwrite = true): static
|
||||
public function fillStateWithNull(): void
|
||||
{
|
||||
$livewire = $this->getLivewire();
|
||||
if (! Arr::has((array) $this->getLivewire(), $this->getStatePath())) {
|
||||
$this->state(null);
|
||||
}
|
||||
|
||||
data_set(
|
||||
$livewire,
|
||||
$this->getStatePath(),
|
||||
null,
|
||||
$shouldOverwrite,
|
||||
);
|
||||
|
||||
return $this;
|
||||
foreach ($this->getChildComponentContainers(withHidden: true) as $container) {
|
||||
$container->fillStateWithNull();
|
||||
}
|
||||
}
|
||||
|
||||
public function mutateDehydratedState($state)
|
||||
@@ -236,7 +300,7 @@ trait HasState
|
||||
|
||||
return data_get(
|
||||
$livewire,
|
||||
$this->generateStatePathForCallback($path, $isAbsolute)
|
||||
$this->generateRelativeStatePath($path, $isAbsolute)
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -248,7 +312,7 @@ trait HasState
|
||||
|
||||
data_set(
|
||||
$livewire,
|
||||
$this->generateStatePathForCallback($path, $isAbsolute),
|
||||
$this->generateRelativeStatePath($path, $isAbsolute),
|
||||
$this->evaluate($state),
|
||||
);
|
||||
|
||||
@@ -256,7 +320,7 @@ trait HasState
|
||||
};
|
||||
}
|
||||
|
||||
protected function generateStatePathForCallback(string | Component $path, bool $isAbsolute = false): string
|
||||
protected function generateRelativeStatePath(string | Component $path, bool $isAbsolute = false): string
|
||||
{
|
||||
if ($path instanceof Component) {
|
||||
return $path->getStatePath();
|
||||
|
||||
@@ -4,9 +4,7 @@ namespace Filament\Forms\Components\Contracts;
|
||||
|
||||
interface HasValidationRules
|
||||
{
|
||||
public function getStatePath(): string;
|
||||
public function dehydrateValidationAttributes(array &$attributes): void;
|
||||
|
||||
public function getValidationAttribute(): string;
|
||||
|
||||
public function getValidationRules(): array;
|
||||
public function dehydrateValidationRules(array &$rules): void;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,8 @@ class KeyValue extends Field
|
||||
|
||||
protected string | Closure | null $valuePlaceholder = null;
|
||||
|
||||
protected bool | Closure $isReorderable = false;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
@@ -123,6 +125,13 @@ class KeyValue extends Field
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function reorderable(bool | Closure $condition = true): static
|
||||
{
|
||||
$this->isReorderable = $condition;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function canAddRows(): bool
|
||||
{
|
||||
return ! $this->evaluate($this->shouldDisableAddingRows);
|
||||
@@ -172,4 +181,9 @@ class KeyValue extends Field
|
||||
{
|
||||
return $this->evaluate($this->valuePlaceholder);
|
||||
}
|
||||
|
||||
public function isReorderable(): bool
|
||||
{
|
||||
return $this->evaluate($this->isReorderable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +406,7 @@ class Repeater extends Field
|
||||
|
||||
return $records
|
||||
->map(function (Model $record) use ($activeLocale): array {
|
||||
$state = $record->toArray();
|
||||
$state = $record->attributesToArray();
|
||||
|
||||
if ($activeLocale && method_exists($record, 'getTranslatableAttributes') && method_exists($record, 'getTranslation')) {
|
||||
foreach ($record->getTranslatableAttributes() as $attribute) {
|
||||
|
||||
@@ -20,6 +20,7 @@ use Illuminate\Validation\Rules\Exists;
|
||||
class Select extends Field
|
||||
{
|
||||
use Concerns\HasAffixes {
|
||||
getActions as getBaseActions;
|
||||
getSuffixAction as getBaseSuffixAction;
|
||||
}
|
||||
use Concerns\HasExtraInputAttributes;
|
||||
@@ -170,7 +171,7 @@ class Select extends Field
|
||||
|
||||
public function getActions(): array
|
||||
{
|
||||
$actions = parent::getActions();
|
||||
$actions = $this->getBaseActions();
|
||||
|
||||
$createOptionActionName = $this->getCreateOptionActionName();
|
||||
|
||||
@@ -254,7 +255,7 @@ class Select extends Field
|
||||
if ($this->modifyCreateOptionActionUsing) {
|
||||
$action = $this->evaluate($this->modifyCreateOptionActionUsing, [
|
||||
'action' => $action,
|
||||
]);
|
||||
]) ?? $action;
|
||||
}
|
||||
|
||||
return $action;
|
||||
|
||||
@@ -98,7 +98,7 @@ class TextInput extends Field implements Contracts\CanBeLengthConstrained, CanHa
|
||||
$value = $component->getMaxValue();
|
||||
|
||||
return "max:{$value}";
|
||||
});
|
||||
}, static fn (TextInput $component): bool => filled($component->getMaxValue()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -111,7 +111,7 @@ class TextInput extends Field implements Contracts\CanBeLengthConstrained, CanHa
|
||||
$value = $component->getMinValue();
|
||||
|
||||
return "min:{$value}";
|
||||
});
|
||||
}, static fn (TextInput $component): bool => filled($component->getMinValue()));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user