Units

Units are named collections of pure functions, available globally across all components and callable directly from bind expressions and text interpolations. Define a utility once, use it anywhere.

explanation

Why Units Exist

Utility functions belong in one place, not scattered across every component that needs them. Units let you define formatting helpers, math operations, or string utilities once and call them from any component's template — without adding methods to individual abstractions just to make them reachable.

This is needed because WakaPAC's expression parser runs in an isolated scope: it evaluates bind expressions and text interpolations against the component's abstraction, not the JavaScript global scope. Units are the deliberate bridge — a clean, explicit way to expose shared functions to templates without opening the parser to arbitrary global access.

How Units Work

Units follow the Pascal uses convention: each unit defines its own namespace, and components can opt in to flat access via data-pac-uses. A unit lives in its own JS file and is registered on the page via wakaPAC.use():

<script src="waka-unit-math.js"></script>
<script>
    wakaPAC.use(wakaMath);
</script>
Load order: Unit files must be loaded after wakapac.js and before the page initializes its components. Use conventional <script> tags in document order.

Namespaced Calls

Once registered, a unit's functions are available in any bind expression or text interpolation using UnitName.function() syntax. No component-level declaration is required.

{{ Math.round(price) }}
{{ StringUtils.capitalize(name) }}
Always call unit functions with parentheses. In click bindings, a bare reference like click: Stdlib.beep resolves to the function itself rather than calling it, and WakaPAC will invoke it with the DOM event as the first argument — which is unlikely to be what you want. Always write click: Stdlib.beep().

Flat Calls via data-pac-uses

Declare data-pac-uses on a component container to import a unit's functions into its flat namespace, dropping the unit prefix entirely. Useful when a unit is used heavily throughout a component's template.

<div data-pac-id="myComponent" data-pac-uses="StringUtils">
    {{ truncate(title, 50) }}
    {{ capitalize(name) }}
</div>

Import multiple units by separating their names with commas:

<div data-pac-id="myComponent" data-pac-uses="StringUtils, WakaMath">
    {{ truncate(title, 50) }}
    {{ round(price) }}
</div>
Name collisions: When multiple imported units define a function with the same name, the last unit listed in data-pac-uses wins. Use namespaced calls to disambiguate.

Variable Shadowing

A data property on the abstraction takes precedence over a registered unit of the same name. If the abstraction defines a StringUtils property, StringUtils.truncate() in a template resolves to that property — not the unit.

// Abstraction with a property named 'StringUtils'
{
    StringUtils: {
        truncate: () => 'I am a data property'
    }
}

// In template: StringUtils.truncate() → calls the data property, not the unit

WakaPAC logs a console warning when a data property shadows a registered unit name, making the conflict easy to diagnose.

Writing a Unit

Unit functions receive pre-evaluated plain JavaScript values as arguments — no toolkit knowledge required. Wrap the unit in an IIFE, assign it to window, and implement createPacPlugin to return the unit's name and functions:

(function() {
    "use strict";

    window.MyUnit = {
        createPacPlugin(pac, options = {}) {
            return {
                name: 'MyUnit',
                functions: {
                    double: (n) => n * 2,
                    greet: (name) => 'Hello, ' + name + '!'
                }
            };
        }
    };

})();

Then register it on the page after loading the unit file:

wakaPAC.use(MyUnit);

Units that require configuration accept an options object as a second argument to wakaPAC.use(). The options are forwarded to createPacPlugin as the second parameter:

wakaPAC.use(DateUtils, { locale: 'nl-NL' });

Using Units as Helper Libraries

Units can also be used as imperative helper libraries within component methods, not just in templates. Call wakaPAC.unit() to retrieve a unit's function set by name or by library reference — both forms return the same object.

const cu = wakaPAC.unit('CollectionUtils');
const cu = wakaPAC.unit(CollectionUtils);   // equivalent

The returned object is the same functions object the unit exposes to templates, so all functions are immediately available:

wakaPAC('#myComponent', {
    init() {
        const str = wakaPAC.unit('StringUtils');
        console.log(str.truncate(this.title, 50));
        console.log(str.slug(this.name));
    }
});

Returns null if the unit name is not registered.

Supplied Units

WakaPAC ships with Stdlib built in and a set of optional unit files for common use cases. Optional units are standalone files that must be loaded and registered explicitly.

Stdlib

General-purpose utility functions under the Stdlib namespace. Stdlib is built into wakapac.js and registered automatically — no separate file or wakaPAC.use() call required.

FunctionSignatureDescription
loglog(...args)Logs values to the browser console. Useful for inspecting abstraction state from within bind expressions.
beepbeep(frequency=440, duration=200, volume=0.5, type='square')Plays an audible tone via the Web Audio API. Requires a prior user gesture. Oscillator type: sine, square, sawtooth, or triangle.
sendMessagesendMessage(pacId, message, wParam=0, lParam=0, extended={})Sends a message to a specific WakaPAC component by id. Equivalent to wakaPAC.sendMessage() but callable from bind expressions.
sendMessageToParentsendMessageToParent(pacId, message, wParam=0, lParam=0, extended={})Sends a message to the parent component of the given container. Equivalent to wakaPAC.sendMessageToParent() but callable from bind expressions.
broadcastMessagebroadcastMessage(message, wParam=0, lParam=0, extended={})Broadcasts a message to all active WakaPAC components. Equivalent to wakaPAC.broadcastMessage() but callable from bind expressions.
Note: log and beep are side-effect functions and not meaningful in {{ }} interpolations. Use them in event bindings: data-pac-bind="click: Stdlib.log(price)". As with all unit functions in click bindings, parentheses are required.

Math (waka-unit-math.js)

Exposes common mathematical operations under the Math namespace.

wakaPAC.use(WakaMath);
FunctionSignatureDescription
absabs(n)Absolute value
ceilceil(n)Round up to nearest integer
floorfloor(n)Round down to nearest integer
roundround(n)Round to nearest integer
minmin(...args)Smallest of the given values
maxmax(...args)Largest of the given values
clampclamp(n, lo, hi)Clamp n between lo and hi
powpow(base, exp)Base raised to the power of exp
sqrtsqrt(n)Square root
signsign(n)Sign of a number: 1, -1, or 0
trunctrunc(n)Integer part, discarding fractional digits
PIPI()Value of π
randomrandom()Pseudo-random number between 0 and 1. Re-evaluates on every render cycle — use sparingly in binds.
roundToroundTo(n, decimals)Round to a given number of decimal places
inRangeinRange(n, lo, hi)Returns true if n is within the inclusive range [lo, hi]
toRadtoRad(deg)Converts degrees to radians.
toDegtoDeg(rad)Converts radians to degrees.

StringUtils (waka-unit-stringutils.js)

Exposes common string operations under the StringUtils namespace.

wakaPAC.use(StringUtils);
FunctionSignatureDescription
capitalizecapitalize(s)First character uppercased, rest lowercased
upperupper(s)Convert to uppercase
lowerlower(s)Convert to lowercase
trimtrim(s)Remove leading and trailing whitespace
truncatetruncate(s, max, ellipsis='…')Truncate to max characters, appending ellipsis if cut
replacereplace(s, search, replacement)Replace first occurrence of substring
replaceAllreplaceAll(s, search, replacement)Replace all occurrences of substring
startsWithstartsWith(s, search)Returns true if string starts with search
endsWithendsWith(s, search)Returns true if string ends with search
containscontains(s, search)Returns true if string contains search
padStartpadStart(s, length, fill=' ')Pad the start to target length
padEndpadEnd(s, length, fill=' ')Pad the end to target length
repeatrepeat(s, count)Repeat string count times
lengthlength(s)Number of characters in the string
slugslug(s)Convert to URL-friendly slug
countStrcountStr(substr, s)Number of non-overlapping occurrences of substr in s (case-sensitive)
containsTextcontainsText(s, substr)Returns true if s contains substr, ignoring case
startsTextstartsText(s, substr)Returns true if s starts with substr, ignoring case
endsTextendsText(s, substr)Returns true if s ends with substr, ignoring case
reverseStringreverseString(s)Reverses the characters in a string

DateUtils (waka-unit-dateutils.js)

Exposes date formatting, arithmetic, and comparison operations under the DateUtils namespace. Backed entirely by the native Intl API — no external libraries required. Accepts an optional locale at registration time; defaults to the browser's locale.

wakaPAC.use(DateUtils);                        // browser default locale
wakaPAC.use(DateUtils, { locale: 'nl-NL' });   // explicit locale

All functions accept a Date object, a numeric timestamp, or a date string. Invalid or missing input returns '' or null rather than throwing.

FunctionSignatureDescription
formatShortformatShort(date)Short locale date string (e.g. "29-3-2026")
formatMediumformatMedium(date)Medium locale date string (e.g. "29 mrt 2026")
formatLongformatLong(date)Long locale date string (e.g. "29 maart 2026")
formatDateTimeformatDateTime(date)Medium date with short time (e.g. "29 mrt 2026 14:05")
formatTimeformatTime(date)Time only (e.g. "14:05")
fromNowfromNow(date)Relative time string (e.g. "3 days ago", "in 2 hours")
yearyear(date)Full year as integer (e.g. 2026)
monthmonth(date)Month as 1-based integer (1–12)
dayday(date)Day of month (1–31)
monthNamemonthName(date)Locale-aware month name (e.g. "maart")
weekdayweekday(date)Locale-aware weekday name (e.g. "zondag")
todaytoday()Today's date at midnight
nownow()Current date and time. Re-evaluates on every render cycle — use sparingly in binds.
isPastisPast(date)Returns true if the date is in the past
isFutureisFuture(date)Returns true if the date is in the future
isSameDayisSameDay(a, b)Returns true if two dates fall on the same calendar day
isWeekendisWeekend(date)Returns true if the date falls on Saturday or Sunday
isBetweenisBetween(date, start, end)Returns true if date falls between start and end (inclusive)
diffDaysdiffDays(a, b)Difference in whole days between two dates. Positive if b is after a.
addDaysaddDays(date, n)New date with n days added (negative to subtract)
addMonthsaddMonths(date, n)New date with n months added, clamped to last valid day
addYearsaddYears(date, n)New date with n years added, clamped to last valid day
startOfstartOf(date, unit)Start of 'day', 'week', 'month', or 'year' (00:00:00.000). Week starts Monday.
endOfendOf(date, unit)End of 'day', 'week', 'month', or 'year' (23:59:59.999). Week ends Sunday.
weekNumberweekNumber(date)ISO week number of the year (1–53)
quarterquarter(date)Quarter of the year (1–4)
yearsBetweenyearsBetween(a, b)Whole years between two dates. Positive if b is after a.
monthsBetweenmonthsBetween(a, b)Whole months between two dates. Positive if b is after a.
weeksBetweenweeksBetween(a, b)Whole weeks between two dates. Positive if b is after a.

TypeUtils (waka-unit-typeutils.js)

Exposes type checking and type coercion functions under the TypeUtils namespace. Useful in bind conditions where typeof and strict equality checks are not available in template expressions.

wakaPAC.use(TypeUtils);
FunctionSignatureDescription
Type Checking
isNullisNull(value)Returns true if value is null or undefined
isDefinedisDefined(value)Returns true if value is not null and not undefined
isStringisString(value)Returns true if value is a string
isNumberisNumber(value)Returns true if value is a finite number (excludes NaN and Infinity)
isIntisInt(value)Returns true if value is an integer
isBoolisBool(value)Returns true if value is a boolean
isArrayisArray(value)Returns true if value is an array
isObjectisObject(value)Returns true if value is a plain object (not array, Date, or null)
isDateisDate(value)Returns true if value is a valid Date object
isFunctionisFunction(value)Returns true if value is a function
Emptiness
isEmptyisEmpty(value)Returns true for null, undefined, empty string, empty array, object with no own keys, or 0
isNaNisNaN(value)Returns true if value is NaN
Type Coercion
toInttoInt(value, radix=10)Convert to integer; returns null if not parseable
toFloattoFloat(value)Convert to float; returns null if not parseable
toBooltoBool(value)Convert to boolean. Strings 'true', '1', 'yes', 'on' → true; 'false', '0', 'no', 'off' → false
toStringtoString(value)Convert to string; null and undefined return '' rather than 'null'/'undefined'
coalescecoalesce(...args)Returns the first non-null, non-undefined argument
typeOftypeOf(value)Returns type as string: 'null', 'array', 'date', or standard typeof values

PhpUtils (waka-unit-phputils.js)

Exposes a curated set of PHP-style utility functions under the PhpUtils namespace. Functions follow PHP naming conventions and signatures. in_array always uses strict comparison (===).

wakaPAC.use(PhpUtils);
FunctionSignatureDescription
String
nl2brnl2br(s)Insert <br> before each newline
wordwrapwordwrap(s, width=75, breakStr='\n', cutLongWords=false)Wrap string to given character width
str_padstr_pad(s, length, padStr=' ', type=0)Pad string to length. type: 0 = right, 1 = left, 2 = both
str_repeatstr_repeat(s, times)Repeat string times times
str_word_countstr_word_count(s)Number of words in a string
ucwordsucwords(s)Uppercase first character of each word
lcfirstlcfirst(s)Lowercase first character of a string
Number
number_formatnumber_format(n, decimals=0, decPoint='.', thousandsSep=',')Format number with grouped thousands and decimal point
Array
implodeimplode(glue, arr)Join array elements into a string
countcount(value)Number of elements in an array or string, or own keys of an object
in_arrayin_array(needle, haystack)Returns true if needle exists in haystack (strict comparison)
array_key_existsarray_key_exists(key, value)Returns true if key exists in array or object

NumberUtils (waka-unit-numberutils.js)

Exposes number formatting operations under the NumberUtils namespace. Backed entirely by the native Intl API — no external libraries required. Accepts an optional locale at registration time; defaults to the browser's locale.

wakaPAC.use(NumberUtils);                        // browser default locale
wakaPAC.use(NumberUtils, { locale: 'nl-NL' });   // explicit locale
percent() input: percent() expects a fractional value — pass 0.75 to get "75%", not 75. This matches the Intl.NumberFormat percent style convention.
FunctionSignatureDescription
formatformat(n, decimals=0)Locale-formatted number with grouped thousands (e.g. "1.200" in nl-NL)
currencycurrency(n, currency='EUR', display='symbol')Currency value. display: 'symbol' (€), 'code' (EUR), or 'name' (euro)
percentpercent(n, decimals=0)Percentage from fractional input (0.75 → "75%")
compactcompact(n, display='short')Compact notation (e.g. 1400 → "1.4K"). display: 'short' or 'long'
durationduration(totalSeconds)Human-readable duration (e.g. 3661 → "1h 01m 01s"). Leading zero units omitted.

CollectionUtils (waka-unit-collectionutils.js)

Exposes array and collection operations under the CollectionUtils namespace. Key paths support dot-notation for nested property access (e.g. 'address.city').

wakaPAC.use(CollectionUtils);
FunctionSignatureDescription
groupBygroupBy(arr, key)Groups items by the value at key path. Returns an object keyed by group value.
sortBysortBy(arr, key, dir='asc')Returns a new array sorted by key path. dir: 'asc' or 'desc'.
uniqueunique(arr, key?)Removes duplicates. Without key uses strict equality; with key deduplicates by that property.
countBycountBy(arr, key)Returns an object mapping each distinct value at key to its count.
chunkchunk(arr, size)Splits array into chunks of the given size.
flattenflatten(arr)Flattens one level of nesting.
zipzip(a, b)Interleaves two arrays into [a, b] pairs, stopping at the shorter length.
sumsum(arr, key)Sum of values at key path. Non-numeric values skipped.
avgavg(arr, key)Average of values at key path. Returns null if no numeric values found.
minmin(arr, key)Minimum value at key path. Returns null if no numeric values found.
maxmax(arr, key)Maximum value at key path. Returns null if no numeric values found.

RegexUtils (waka-unit-regexutils.js)

Exposes regular expression operations under the RegexUtils namespace. Patterns are parsed and cached on first use — no performance cost on repeated evaluations across render cycles.

wakaPAC.use(RegexUtils);

Patterns can be plain strings or slash-delimited with flags:

'foo'        // plain pattern, no flags
'/foo/i'     // case-insensitive
'/^\d+$/m'   // anchored, multiline
FunctionSignatureDescription
testtest(pattern, s)Returns true if the pattern matches anywhere in the string
matchmatch(pattern, s)Returns the first match string, or null if not found
extractextract(pattern, s, group=1)Returns the value of a capture group. Group 0 = full match, 1 = first capture group.
replacereplace(pattern, s, replacement)Replaces the first match. If the pattern includes the g flag, all matches are replaced.
replaceAllreplaceAll(pattern, s, replacement)Replaces all matches. Forces the g flag regardless of the pattern.

ColorUtils (waka-unit-colorutils.js)

Exposes color manipulation functions under the ColorUtils namespace. All functions accept hex color strings in #rgb or #rrggbb format and return empty string on invalid input rather than throwing.

wakaPAC.use(ColorUtils);

Blend amounts are fractional values between 0 and 1, where 0 leaves the color unchanged and 1 produces the pure target color (white for lighten, black for darken).

FunctionSignatureDescription
lightenlighten(hex, amount=0.2)Mix the color toward white by the given ratio. Returns a hex string.
darkendarken(hex, amount=0.2)Mix the color toward black by the given ratio. Returns a hex string.
alphaalpha(hex, alpha=1)Return an rgba() CSS string with the given alpha (0–1).
hexToRgbhexToRgb(hex)Convert a hex color to an {r, g, b} object, or '' if invalid.
rgbToHexrgbToHex(r, g, b)Convert RGB channel values (0–255) to a hex color string. Channel values are clamped.

EscapeUtils (waka-unit-escapeutils.js)

Exposes string escaping and sanitization functions under the EscapeUtils namespace. Useful when inserting user-supplied content into HTML output, regular expressions, or URL query strings. null and undefined are coerced to an empty string rather than the literal text "null".

wakaPAC.use(EscapeUtils);
FunctionSignatureDescription
escapeHtmlescapeHtml(value)Escape &, <, >, ", and ' to their HTML entities.
unescapeHtmlunescapeHtml(value)Convert HTML entities back to their literal characters.
escapeRegexescapeRegex(value)Escape all regex metacharacters so the string can be embedded safely in a RegExp.
escapeUrlescapeUrl(value)Percent-encode a value for use as a URL component (wraps encodeURIComponent).
unescapeUrlunescapeUrl(value)Decode a percent-encoded URL component. Returns '' on malformed input instead of throwing.

Math3D (waka-unit-math3d.js)

Exposes 4×4 matrix math, 3-component vector operations, and quaternion support under the Math3D namespace. All matrices are column-major Float32Array instances (16 elements), compatible with gl.uniformMatrix4fv() without transposing. Vectors are plain number[] arrays [x, y, z]. Quaternions are plain number[] arrays [x, y, z, w].

Not for template expressions. Math3D functions return Float32Array objects intended for WebGL uniform calls, not for rendering into the DOM. Use this unit imperatively inside component init() or event handlers, not in {{ }} interpolations or data-pac-bind attributes.
wakaPAC.use(Math3D);

// Retrieve in a component method
const m = wakaPAC.unit(Math3D);

// Build an MVP matrix
const mvp = m.mul(m.perspective(fov, aspect, near, far),
            m.mul(m.translation(0, 0, -4),
            m.mul(m.rotX(rx), m.rotY(ry))));
gl.uniformMatrix4fv(uMVP, false, mvp);

// Normal matrix for lighting (corrects normals under non-uniform scale)
const normalMatrix = m.transpose(m.invert(modelMatrix));
gl.uniformMatrix4fv(uNormal, false, normalMatrix);

// Smooth rotation via quaternion slerp
const q = m.quatSlerp(qA, qB, t);
gl.uniformMatrix4fv(uRot, false, m.fromQuat(q));

MAT4 — 4×4 matrix operations

FunctionSignatureDescription
identityidentity()Returns a new 4×4 identity matrix.
mulmul(a, b)Returns a × b. Transforms apply right-to-left: mul(view, model) applies model first.
invertinvert(m)Returns the inverse of m, or null if the matrix is singular (determinant ≈ 0). Always null-check the result before passing to gl.uniformMatrix4fv(). Used with transpose() to produce normal matrices for lighting.
transposetranspose(m)Returns the transpose of m (rows and columns swapped).
perspectiveperspective(fovY, aspect, near, far)Perspective projection matrix. fovY is vertical field of view in radians. aspect is width / height. near must be > 0.
orthoortho(left, right, bottom, top, near, far)Orthographic projection matrix. No foreshortening — useful for 2D overlays and UI elements.
frustumfrustum(left, right, bottom, top, near, far)Frustum projection matrix. Lower-level alternative to perspective() for asymmetric frustums (e.g. VR, off-axis projections).
translationtranslation(x, y, z)Translation matrix. Moves geometry by (x, y, z) in world space.
scalescale(x, y, z)Scale matrix. Non-uniform scale changes surface normals — pair with a normal matrix in shaders.
rotXrotX(angle)Rotation matrix around the X axis. angle in radians.
rotYrotY(angle)Rotation matrix around the Y axis. angle in radians.
rotZrotZ(angle)Rotation matrix around the Z axis. angle in radians.
lookAtlookAt(eye, target, up)View matrix from camera position, look-at target, and up hint (typically [0,1,0]). All three are [x,y,z] arrays. If the forward direction is parallel to the up hint (camera looking straight up or down), a fallback up vector is used silently — the camera keeps working but its roll orientation is arbitrary.
fromQuatfromQuat(q)Rotation matrix from a unit quaternion [x,y,z,w]. Use with quatSlerp() for smooth interpolated rotations.
mat4Clonemat4Clone(m)Returns a deep copy of a 4×4 matrix. Float32Arrays are reference types — use this before branching transforms from a shared base matrix to avoid corrupting the original.

VEC3 — 3-component vector operations

FunctionSignatureDescription
vec3Addvec3Add(a, b)Returns a + b.
vec3Subtractvec3Subtract(a, b)Returns a − b.
vec3Scalevec3Scale(v, s)Returns v scaled by scalar s.
vec3Dotvec3Dot(a, b)Dot product of a and b. Returns a scalar.
vec3Crossvec3Cross(a, b)Cross product a × b. Result is perpendicular to both inputs.
vec3Lengthvec3Length(v)Euclidean length of v.
vec3Normalizevec3Normalize(v)Unit-length version of v. Returns [0,0,0] for zero-length input.
vec3Lerpvec3Lerp(a, b, t)Linearly interpolates between a and b. t=0 returns a, t=1 returns b.
vec3Distancevec3Distance(a, b)Euclidean distance between two points.

QUAT — quaternion operations

Quaternions are [x, y, z, w] arrays where (x, y, z) is the imaginary vector part and w is the real scalar. Unit quaternions (length = 1) represent 3D rotations without gimbal lock.

FunctionSignatureDescription
quatIdentityquatIdentity()Returns the identity quaternion [0,0,0,1] — no rotation.
quatFromEulerquatFromEuler(x, y, z)Quaternion from Euler angles in radians, applied in X → Y → Z order.
quatFromAxisAnglequatFromAxisAngle(axis, angle)Quaternion for a rotation of angle radians around axis. The axis need not be unit length.
quatMulquatMul(a, b)Returns a × b (Hamilton product). Applies rotation b first, then a — same right-to-left convention as matrix multiplication.
quatNormalizequatNormalize(q)Returns the unit-length version of q. Call periodically to correct floating-point drift from repeated quatMul calls. Returns identity for near-zero input.
quatConjugatequatConjugate(q)Negates the vector part (x, y, z), leaving w unchanged. For unit quaternions this equals the inverse and represents the opposite rotation. Prefer this over quatInvert() whenever the quaternion is known to be unit length — it is a simple negation with no division.
quatInvertquatInvert(q)Full quaternion inverse: conjugate divided by squared length. Use only when the quaternion may not be unit length. For unit quaternions, quatConjugate() is equivalent and cheaper. Returns identity for near-zero input.
quatSlerpquatSlerp(a, b, t)Spherical interpolation between two unit quaternions. Produces smooth, constant-speed rotation without gimbal lock. t=0 returns a, t=1 returns b.