PHPackages                             osama-dev/filament-calculator-action - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. osama-dev/filament-calculator-action

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

osama-dev/filament-calculator-action
====================================

Dynamic real-time calculator action for Filament v3 &amp; v4 — instant client-side arithmetic, zero Livewire round-trips

v1.1.0(2mo ago)1482MITPHPPHP ^8.1

Since Feb 22Pushed 2mo agoCompare

[ Source](https://github.com/osamaatef1/filament-calculator-action)[ Packagist](https://packagist.org/packages/osama-dev/filament-calculator-action)[ RSS](/packages/osama-dev-filament-calculator-action/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependencies (5)Versions (10)Used By (0)

filament-calculator-action
==========================

[](#filament-calculator-action)

[![Latest Version on Packagist](https://camo.githubusercontent.com/7e17870d3d5c5f77c5c702e69fb0cf1daf50b3727d765b7666535ce32b7865ed/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6f73616d612d6465762f66696c616d656e742d63616c63756c61746f722d616374696f6e2e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/osama-dev/filament-calculator-action)[![License](https://camo.githubusercontent.com/b2d947a22c5d4bdc766e9e4f42a4693ac5d740f2705136db00d89fa752aa9762/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6f73616d612d6465762f66696c616d656e742d63616c63756c61746f722d616374696f6e2e7376673f7374796c653d666c61742d737175617265)](LICENSE.md)

A plug-and-play real-time calculator action for **Filament v3 &amp; v4** — instant client-side arithmetic with zero Livewire round-trips.

Works in both **table row actions** (`CalculatorAction`) and **page header actions** (`CalculatorPageAction`).

---

Why this package?
-----------------

[](#why-this-package)

Using `->live()` on Filament form fields triggers a Livewire server round-trip on every keystroke. For a simple numeric calculator (subtotal + fees − discount = total), that's a server call every time the user types a digit — sluggish and unnecessary.

This package generates lightweight inline JavaScript (`onkeyup` / `onchange`) that performs all arithmetic directly in the browser. No server call. No debounce. No waiting. The result field updates the instant a key is released.

Server-side recomputation via `computeResult()` is still performed inside `->action()` to ensure the stored value is always trustworthy.

---

Installation
------------

[](#installation)

```
composer require osama-dev/filament-calculator-action
```

No extra configuration needed — the service provider is auto-discovered.

---

Two action classes
------------------

[](#two-action-classes)

ClassExtendsUse in`CalculatorAction``Filament\Tables\Actions\Action` (v3) / `Filament\Actions\Action` (v4)Table row actions`CalculatorPageAction``Filament\Actions\Action`Page header actions (`getHeaderActions()`)Both share the exact same API via the `HasCalculation` trait.

---

Basic Usage — Table Action
--------------------------

[](#basic-usage--table-action)

```
use OsamaDev\FilamentCalculatorAction\CalculatorAction;
use OsamaDev\FilamentCalculatorAction\CalcField;
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;

CalculatorAction::make('issue_receipt')
    ->label('Mark Fulfilled & Issue Receipt')
    ->icon('heroicon-o-document-text')
    ->color('success')
    ->visible(fn ($record): bool => $record->status === 'in_progress')
    ->calcSectionHeading('Receipt Details')
    ->calcColumns(2)
    ->calcPrefix('EGP')
    ->calcFields([
        CalcField::make('subtotal')
            ->label('Subtotal')
            ->adds()
            ->required()
            ->helperText('including taxes')
            ->default(fn ($record) => (float) ($record->sub_total ?? 0))
            ->columnSpan(1),
        CalcField::make('extra_fees')
            ->label('Extra Fees')
            ->adds()
            ->default(0)
            ->columnSpan(1),
        CalcField::make('discount')
            ->label('Discount')
            ->subtracts()
            ->default(0)
            ->columnSpan(1),
        CalcField::make('total')
            ->label('Total')
            ->result(),
    ])
    ->form([
        Section::make('Booking Context')
            ->schema([
                TextInput::make('user_name')
                    ->label('User')
                    ->default(fn ($record) => $record->user?->full_name ?? 'Deleted User')
                    ->disabled(),
                TextInput::make('reference')
                    ->label('Reference')
                    ->default(fn ($record) => $record->reference)
                    ->disabled(),
            ])->columns(2),
        Textarea::make('notes')
            ->label('Notes')
            ->rows(3)
            ->columnSpanFull(),
    ])
    ->action(function (array $data, $record) {
        $subtotal = (float) ($data['subtotal'] ?? 0);
        $discount = (float) ($data['discount'] ?? 0);
        $extraFees = (float) ($data['extra_fees'] ?? 0);
        $total = $subtotal - $discount + $extraFees;

        // Or use the helper: $total = $this->computeResult($data);

        $record->invoice()->create([
            'subtotal'   => $subtotal,
            'discount'   => $discount,
            'extra_fees' => $extraFees,
            'total'      => $total,
            'notes'      => $data['notes'] ?? null,
        ]);
    })
    ->modalHeading('Issue Receipt')
    ->modalSubmitActionLabel('Create Receipt')
```

> **Note:** `->form([...])` (v3) or `->schema([...])` (v4) defines your custom fields. The calculator section is always appended **after** your form fields automatically. Both methods work in both versions.

---

Page Header Action
------------------

[](#page-header-action)

Use `CalculatorPageAction` when placing the action in `getHeaderActions()` on a resource page (View, Edit, etc.):

```
use OsamaDev\FilamentCalculatorAction\CalculatorPageAction;
use OsamaDev\FilamentCalculatorAction\CalcField;

protected function getHeaderActions(): array
{
    return [
        CalculatorPageAction::make('issue_receipt')
            ->label('Mark Fulfilled & Issue Receipt')
            ->icon('heroicon-o-document-text')
            ->color('success')
            ->visible(fn ($record): bool => $record->status === 'in_progress')
            ->calcSectionHeading('Receipt Details')
            ->calcPrefix('EGP')
            ->calcColumns(2)
            ->calcFields([
                CalcField::make('subtotal')->label('Subtotal')->adds()->required()->default(fn ($record) => (float) ($record->sub_total ?? 0))->columnSpan(1),
                CalcField::make('extra_fees')->label('Extra Fees')->adds()->default(0)->columnSpan(1),
                CalcField::make('discount')->label('Discount')->subtracts()->default(0)->columnSpan(1),
                CalcField::make('total')->label('Total')->result(),
            ])
            ->action(function (array $data, $record) {
                $total = $this->computeResult($data);
                // persist invoice...
            }),
    ];
}
```

---

Multiply &amp; Divide Example — Quote Builder
---------------------------------------------

[](#multiply--divide-example--quote-builder)

```
CalculatorAction::make('generate_quote')
    ->label('Generate Quote')
    ->calcSectionHeading('Quote Breakdown')
    ->calcColumns(2)
    ->calcPrefix('EGP')
    ->calcFields([
        CalcField::make('unit_price')
            ->label('Unit Price')
            ->adds()
            ->required()
            ->columnSpan(1),
        CalcField::make('quantity')
            ->label('Quantity')
            ->multiplies()
            ->default(1)
            ->columnSpan(1),
        CalcField::make('discount')
            ->label('Discount')
            ->subtracts()
            ->default(0)
            ->columnSpan(1),
        CalcField::make('total')
            ->label('Total')
            ->result(),
    ])
    ->action(function (array $data, $record) {
        $total = $this->computeResult($data);
        // formula: (unit_price - discount) * quantity
    })
```

> **Order of operations:** adds and subtracts are applied first, then multiplies, then divides. So `(unit_price - discount) * quantity / installments` works as expected.

---

Another Example — Payroll Calculator
------------------------------------

[](#another-example--payroll-calculator)

```
CalculatorAction::make('process_payroll')
    ->label('Process Payroll')
    ->calcSectionHeading('Payroll Breakdown')
    ->calcColumns(2)
    ->calcPrefix('USD')
    ->calcFields([
        CalcField::make('base_salary')
            ->label('Base Salary')
            ->adds()
            ->required()
            ->default(5000)
            ->columnSpan(1),
        CalcField::make('bonus')
            ->label('Bonus')
            ->adds()
            ->default(0)
            ->columnSpan(1),
        CalcField::make('deductions')
            ->label('Deductions')
            ->subtracts()
            ->default(0)
            ->columnSpan(1),
        CalcField::make('tax_withholding')
            ->label('Tax Withholding')
            ->subtracts()
            ->default(0)
            ->columnSpan(1),
        CalcField::make('net_salary')
            ->label('Net Salary')
            ->result(),
    ])
    ->action(function (array $data, $record) {
        $net = $this->computeResult($data);

        $record->payroll()->create([
            'base_salary'     => $data['base_salary'],
            'bonus'           => $data['bonus'],
            'deductions'      => $data['deductions'],
            'tax_withholding' => $data['tax_withholding'],
            'net_salary'      => $net,
        ]);
    })
```

---

CalcField API
-------------

[](#calcfield-api)

MethodDescription`CalcField::make(string $name)`Create a field with the given key name`->adds()`Field value is **added** to the running total`->subtracts()`Field value is **subtracted** from the running total`->multiplies()`Running total is **multiplied** by this field's value`->divides()`Running total is **divided** by this field's value (zero-safe)`->result()`Marks this as the **read-only result** display field`->label(string $label)`Label shown above the input`->prefix(string $prefix)`Currency/unit prefix (e.g. `'EGP'`, `'$'`) — falls back to `calcPrefix()``->required(bool $required = true)`Makes the field required on submit`->default(float|Closure $value)`Default value; Closure receives `$record``->columnSpan(int $span)`Grid column span within the calc section (default: `1`)`->helperText(string $text)`Small hint text displayed below the input---

CalculatorAction / CalculatorPageAction API
-------------------------------------------

[](#calculatoraction--calculatorpageaction-api)

MethodDescription`->calcFields(array $fields)`Array of `CalcField` instances`->calcSectionHeading(string $heading)`Section heading for the calculator (default: `'Calculation'`)`->calcColumns(int $columns)`Column count for the calc section grid (default: `2`)`->calcPrefix(string $prefix)`Global prefix applied to all fields that don't define their own`->calcFlash(bool $flash = true)`Enable/disable the yellow highlight animation (default: `true`)`->calcFlashColor(string $color)`Highlight color as any CSS value (default: `'#fef9c3'`)`->calcFlashDuration(int $ms)`Flash animation duration in milliseconds (default: `400`)`->computeResult(array $data)`Server-side recalculation — use inside `->action()`---

How it Works
------------

[](#how-it-works)

Each non-result `CalcField` gets two HTML event attributes: `onkeyup` and `onchange`. Both run the same small inline script that reads all field values via `document.querySelector('[data-calc-field="name"]')`, computes the result, and updates the result field:

```
// Generated JS (simplified)
var __r = ((unit_price) - (discount)) * (quantity);
var __t = document.querySelector('[data-calc-field="total"]');
if (__t) {
    __t.value = __r.toFixed(2);

    // Negative warning — red outline + red text
    if (__r
