Download the PHP package nativephp/mobile-ui without Composer
On this page you can find all versions of the php package nativephp/mobile-ui. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download nativephp/mobile-ui
More information about nativephp/mobile-ui
Files in nativephp/mobile-ui
Package mobile-ui
Short Description Native SwiftUI and Jetpack Composer components for NativePHP
License MIT
Informations about the package mobile-ui
NativeUI Plugin for NativePHP Mobile
A NativePHP Mobile plugin
Installation
Usage
Listening for Events
Theming & Colors
Theme tokens live in config/native-ui.php (publish with
php artisan vendor:publish --tag=native-ui-config). Every authored color —
theme tokens, element color props, and arbitrary-value classes — accepts the
same grammar:
Alpha hex is authored in CSS #RRGGBBAA order; the framework converts to the
native wire format. Dark mode is auto-derived from light (alpha preserved)
unless a dark block overrides specific tokens.
Disabled controls draw from the surface-variant (fill) and
on-surface-variant (label) tokens on both platforms — adjust those two
tokens to tune disabled contrast app-wide.
Icons accept platform enum overrides in Blade, matching the fluent API:
Accessibility
Every element accepts a screen-reader label and an optional hint, via Blade
attributes (a11y-label / a11y-hint, or the camelCase spellings
a11yLabel / a11yHint) or the fluent API (->a11yLabel() / ->a11yHint()).
The label maps to accessibilityLabel on iOS and contentDescription on
Android; the hint maps to accessibilityHint on iOS and is appended to the
content description on Android.
Always set a11y-label on icon-only buttons, chips, and tabs — without
visible text there is nothing for VoiceOver / TalkBack to announce. Icons are
decorative (silent to screen readers) unless given an a11y-label. List items
with a trailing icon button take trailing-a11y-label (fluent:
->trailingA11yLabel()) to label that button separately from the row.
Caret & Selection Reporting
Requires
nativephp/mobile4.0+, which ships thetext_selectioncallback kind — already enforced by this package's composer constraint.
The text inputs (<native:bare-text-input>, <native:outlined-text-input>,
<native:filled-text-input>) can report caret position and text selection back
to PHP via @selectionChange. The handler receives the current text plus the
selection range:
Offsets are Unicode code points into the text (not UTF-16 units or bytes),
so emoji and other astral characters count as one — safe to feed straight into
mb_substr(..., encoding: 'UTF-8').
Events are coalesced on the native side — by default at most one every 150ms
while the caret moves (the trailing position always fires). Tune the window per
input with selection-debounce-ms / selectionDebounceMs (fluent:
->selectionDebounceMs()); when unset, nothing is serialized and the renderer
default applies. A value of 0 (or less) also means "use the default"; positive
values are floored at one frame (16ms).
Every event carries the full current text, and every event costs a bridge frame plus a full component re-render. That is independent of the
native:modelsync mode: pairing@selectionChangewithnative:model.bluror.debouncestill ships the field contents to PHP on the selection cadence, not the model cadence. Budget the debounce window accordingly.
@selectionChange is never emitted for secure inputs. The callback is not
serialized at all when secure is set, and both renderers additionally refuse
to emit — so caret telemetry can't leak password-field context.
Contract details
- Programmatic value pushes. When PHP pushes a new
valueonto the input, the field is replaced wholesale and the caret drops at the end. Both platforms report that immediately as a single(text, length, length)event, bypassing the debounce. A handler that rewrites the bound model will therefore see one follow-up event. - Discontiguous selections (multi-range, iOS) are reported as a single span
from the lowest start to the highest end — the reported range covers the gaps,
so
mb_substr($text, $start, $end - $start)includes text the user did not select. read-onlyinputs report selection on Android (which keeps them focusable for copy) but not on iOS (where read-only implies disabled, so the field never focuses).
A typical use is typeahead / mention triggers, where @change alone can't tell
you where the user is typing:
Date & Time Pickers
<native:date-picker> wraps SwiftUI's DatePicker and Material 3's
DatePicker / TimePicker behind one API.
The value contract
Values cross the bridge as wall-clock ISO 8601 strings with no offset,
shaped by mode:
| mode | wire value | example |
|---|---|---|
date (default) |
Y-m-d |
2026-07-25 |
time |
H:i, always 24-hour |
14:30 |
datetime |
Y-m-d\TH:i |
2026-07-25T14:30 |
No UTC conversion ever crosses the bridge. That is deliberate: it is what
keeps the classic off-by-one-day bug out of the element. Android's
DatePickerState reports UTC-midnight epoch millis and SwiftUI's DatePicker
binds an instant, so each renderer converts on its own side against one
agreed calendar — neither ever ships an instant.
value, min, and max accept an ISO string or any DateTimeInterface
(Carbon included), and a value finer than the mode needs is truncated — so a
datetime column can drive a date-only picker without reformatting:
An empty string clears the selection; an unparseable one throws.
Timezones and internationalization
timezone takes an IANA identifier and names the calendar the picker
operates in — what "today" means for an empty picker, and on iOS the
calendar used to convert between the bound instant and the wall-clock string.
It does not shift the wire value. Set it when your app pins a business
timezone instead of following the device; leave it unset to follow the device.
locale takes a BCP-47 tag and drives display only — month and weekday
names, weekday order, and the default clock convention. It never changes the
wire value, and the wire formatter is pinned to a Gregorian POSIX calendar so
a Buddhist- or Japanese-era locale can't leak a non-Gregorian year onto the
bridge.
hour-format (auto | 12 | 24) overrides the clock convention. auto
resolves from the locale on both platforms — Android asks
getBestDateTimePattern(locale, "jm") rather than reading the device's
24-hour system setting, so the same locale gives the same result either
side.
Display styles
picker-style picks the presentation, mapped to the nearest native idiom.
(It is not called display — that name is already flex/layout display on every
element.)
picker-style |
iOS | Android |
|---|---|---|
compact (default) |
.compact — tap to popover |
trigger field + modal dialog |
inline |
.graphical — embedded calendar |
embedded picker |
wheel |
.wheel — drum |
no drum in Material; falls back to embedded |
Platform notes
title,confirm-label, andcancel-labelare Android only — iOS commits on selection and has no dialog chrome to label. They are still user-visible strings, so pass translated values:->confirmLabel(__('Done')).- On iOS with
picker-style="compact"and no value, a placeholder trigger stands in until first tap, because SwiftUI's compact picker always renders a concrete date and has no empty state. - With
picker-style="inline"and no initial value, neither platform commits the seeded "today" — you get a change event only once the user actually picks. a11y-label/a11y-hintare plumbed on both platforms; the current selection is additionally announced as the control's accessibility value.min/maxare rejected formode="time". Neither platform can enforce a time-of-day range — SwiftUI'sin:bounds an absolute instant, and Material 3'sTimePickerhas no bounds API — so passing them throws rather than silently doing nothing. Validate the chosen time in your component.picker-style="inline"falls back to compact formode="time"on iOS. SwiftUI's.graphicalstyle is date-only. Android embeds the time picker as asked.- Sync-mode modifiers are rejected. A picker commits discretely, so
native:model.blur/native:model.debounce.300mshave nothing to defer; they throw. Use plainnative:model.
Testing
The plugin registers picker vocabulary on the test harness, so screens read in picker terms rather than raw select-change plumbing:
The pick* macros take an ISO string or any DateTimeInterface and
normalize to the wire shape for that mode before dispatching, so a test using
a Carbon instance or a full timestamp still sends exactly what the renderer
would. assertPicker* match on the picker's label.
Macros register only under a test runner, and only on a core whose
TestableComponent is macroable — the same method_exists gate the camera
plugin uses for its FakeBridge macros.
Testing
Theme normalization and config write-back are pure PHP — no device, emulator,
or bridge round-trip required. Theme::load() / Theme::merge() resolve
authored color tokens (Tailwind names, red-300/20 opacity modifiers, CSS
#RRGGBBAA alpha hex) to wire-format hex, auto-derive a dark block, and mirror
the effective set into config('native-ui.theme.…'). You can assert every step
of that in a unit test:
Element color and typography props share the same grammar and serialize the
same way. Elements expose toArray(new CallbackRegistry) (via
NativeElementCollector), so you can assert what lands on the wire:
Keeping Theme::pushToNative() off the wire
Theme::load() / merge() fire a NativeUI.Theme.Set bridge call on every
change. In a full Laravel test app, pushToNative()'s runningUnitTests()
guard suppresses it. In plain Pest (no booted app), that guard can't trip,
so mute the bridge in beforeEach() — the same pattern the plugin's own tests
use — and reset() between tests:
License
MIT