Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save JamieMason/c78e27f4281049a6d9b72f6cf9782fb8 to your computer and use it in GitHub Desktop.
Save JamieMason/c78e27f4281049a6d9b72f6cf9782fb8 to your computer and use it in GitHub Desktop.

Revisions

  1. JamieMason revised this gist Jan 24, 2025. 1 changed file with 6 additions and 0 deletions.
    6 changes: 6 additions & 0 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -132,6 +132,12 @@ export function logInspectionEvent(event: InspectionEvent): void {
    ['actor', event.action.params?.to?.id],
    ]);
    break;
    default:
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['actor', event.actorRef?.id],
    ]);
    }
    break;
    }
  2. JamieMason revised this gist Jan 23, 2025. 1 changed file with 1 addition and 0 deletions.
    1 change: 1 addition & 0 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -35,6 +35,7 @@ const isVisibleByEventName = {
    'xstate.stopChild': true,
    } as const;

    // taken from https://tailscan.com/colors
    const stylesByName = {
    action: 'color:#a78bfa', // violet 400
    spawn: 'color:#a3e635', // green 400
  3. JamieMason revised this gist Jan 23, 2025. 1 changed file with 14 additions and 0 deletions.
    14 changes: 14 additions & 0 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -23,6 +23,18 @@ You'll notice type errors such as `ts: Property 'id' does not exist on type 'Act
    ```ts
    import type { InspectionEvent, StateValue } from 'xstate';

    const isVisibleByEventName = {
    '@xstate.action': true,
    '@xstate.actor': false,
    '@xstate.event': true,
    '@xstate.microstep': true,
    '@xstate.snapshot': false,
    'xstate.emit': true,
    'xstate.sendTo': true,
    'xstate.spawnChild': true,
    'xstate.stopChild': true,
    } as const;

    const stylesByName = {
    action: 'color:#a78bfa', // violet 400
    spawn: 'color:#a3e635', // green 400
    @@ -35,6 +47,7 @@ const stylesByName = {
    } as const;

    export function logInspectionEvent(event: InspectionEvent): void {
    if (isVisibleByEventName[event.type] === false) return;
    switch (event.type) {
    case '@xstate.snapshot': {
    logWithColor(event, [
    @@ -80,6 +93,7 @@ export function logInspectionEvent(event: InspectionEvent): void {
    break;
    }
    case '@xstate.action': {
    if (isVisibleByEventName[event.action?.type] === false) return;
    switch (event.action?.type) {
    case 'xstate.emit':
    logWithColor(event, [
  4. JamieMason revised this gist Jan 23, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -75,7 +75,7 @@ export function logInspectionEvent(event: InspectionEvent): void {
    ['internal', event.type],
    ['event', event.event?.type],
    ['actor', event.actorRef?.id],
    ['internal', `\n${stateNotation}`],
    ['internal', `\n ${stateNotation}`],
    ]);
    break;
    }
  5. JamieMason revised this gist Jan 23, 2025. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -70,11 +70,12 @@ export function logInspectionEvent(event: InspectionEvent): void {
    break;
    }
    case '@xstate.microstep': {
    const stateNotation = getStateNotation(event.snapshot?.value).sort().join('\n ');
    logWithColor(event, [
    ['internal', event.type],
    ['event', event.event?.type],
    ['actor', event.actorRef?.id],
    ['internal', getStateNotation(event.snapshot?.value).sort().join('\n ')],
    ['internal', `\n${stateNotation}`],
    ]);
    break;
    }
  6. JamieMason revised this gist Jan 23, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ A small `inspect` function to log events to the browser console, when you expand

    ## Screenshot

    ![Screenshot](https://gist.github.com/user-attachments/assets/29890e7e-04cf-46be-bb41-b43c6184485c)
    ![Screenshot](https://gist.github.com/user-attachments/assets/0a6c4e6e-2307-4695-a853-8d24a4fe1a87)

    ## Usage

  7. JamieMason revised this gist Jan 23, 2025. 1 changed file with 44 additions and 28 deletions.
    72 changes: 44 additions & 28 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -24,19 +24,22 @@ You'll notice type errors such as `ts: Property 'id' does not exist on type 'Act
    import type { InspectionEvent, StateValue } from 'xstate';

    const stylesByName = {
    action: 'color:#F48FB1',
    action: 'color:#a78bfa', // violet 400
    spawn: 'color:#a3e635', // green 400
    stop: 'color:#f87171', // red 400
    actor: 'color:inherit',
    arrow: 'color:inherit',
    event: 'color:#81D4FA',
    internal: 'color:#999',
    event: 'color:#38bdf8', // sky 400
    internal: 'color:#94a3b8', // slate 400
    snapshot: 'color:inherit',
    } as const;

    export function logInspectionEvent(event: InspectionEvent): void {
    switch (event.type) {
    case '@xstate.snapshot': {
    logWithColor(event, [
    ['internal', event.type],
    ['actor', event.actorRef?.id],
    ['snapshot', event.actorRef?.id],
    ]);
    break;
    }
    @@ -76,30 +79,43 @@ export function logInspectionEvent(event: InspectionEvent): void {
    break;
    }
    case '@xstate.action': {
    if (event.action?.type === 'xstate.emit') {
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['event', event.action.params?.event?.type],
    ['actor', event.actorRef?.id],
    ]);
    } else if (event.action?.type === 'xstate.stopChild' || event.action?.type === 'xstate.spawnChild') {
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['actor', event.actorRef?.id],
    ['arrow', ''],
    ['actor', event.action.params?.src],
    ]);
    } else if (event.action?.type === 'xstate.sendTo') {
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['event', event.action.params?.event?.type],
    ['actor', event.actorRef?.id],
    ['arrow', ''],
    ['actor', event.action.params?.to?.id],
    ]);
    switch (event.action?.type) {
    case 'xstate.emit':
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['event', event.action.params?.event?.type],
    ['actor', event.actorRef?.id],
    ]);
    break;
    case 'xstate.stopChild':
    logWithColor(event, [
    ['internal', event.type],
    ['stop', event.action?.type],
    ['actor', event.actorRef?.id],
    ['arrow', ''],
    ['actor', event.action.params?.src],
    ]);
    break;
    case 'xstate.spawnChild':
    logWithColor(event, [
    ['internal', event.type],
    ['spawn', event.action?.type],
    ['actor', event.actorRef?.id],
    ['arrow', ''],
    ['actor', event.action.params?.src],
    ]);
    break;
    case 'xstate.sendTo':
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['event', event.action.params?.event?.type],
    ['actor', event.actorRef?.id],
    ['arrow', ''],
    ['actor', event.action.params?.to?.id],
    ]);
    break;
    }
    break;
    }
  8. JamieMason revised this gist Jan 23, 2025. 1 changed file with 67 additions and 39 deletions.
    106 changes: 67 additions & 39 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -16,79 +16,107 @@ const [state, send, actor] = useActor(myMachine, {

    ## Implementation

    I know right 🤮, mainly this is down to [the API for applying colours to browser console logs](https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output) and this is about 30 minutes' work.
    Adds colours to the browser console output, see [MDN: Styling console output](https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output).

    If you do any clean up on this, please comment and let me know. You'll notice there is a `ts: Property 'id' does not exist on type 'ActorRefLike'` – but they can have an ID.
    You'll notice type errors such as `ts: Property 'id' does not exist on type 'ActorRefLike'` – but they can have an ID.

    ```ts
    import type { InspectionEvent, StateValue } from 'xstate';

    export function logInspectionEvent(event: InspectionEvent): void {
    const internalEvent = event.type;
    const style = {
    action: 'color:#F48FB1',
    actor: 'color:inherit',
    eventType: 'color:#81D4FA',
    internalEvent: 'color:#999',
    } as const;
    const stylesByName = {
    action: 'color:#F48FB1',
    actor: 'color:inherit',
    arrow: 'color:inherit',
    event: 'color:#81D4FA',
    internal: 'color:#999',
    } as const;

    export function logInspectionEvent(event: InspectionEvent): void {
    switch (event.type) {
    case '@xstate.snapshot': {
    const actor = event.actorRef?.id;
    console.groupCollapsed(`%c${internalEvent} %c${actor}`, style.internalEvent, style.actor);
    logWithColor(event, [
    ['internal', event.type],
    ['actor', event.actorRef?.id],
    ]);
    break;
    }
    case '@xstate.event': {
    const eventType = event.event?.type;
    const sourceId = event.sourceRef?.id;
    const actorId = event.actorRef?.id;
    if (sourceId && actorId) {
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${sourceId} ➤ ${actorId}`, style.internalEvent, style.eventType, style.actor);
    if (event.sourceRef?.id && event.actorRef?.id) {
    logWithColor(event, [
    ['internal', event.type],
    ['event', event.event?.type],
    ['actor', event.sourceRef?.id],
    ['internal', ''],
    ['actor', event.actorRef?.id],
    ]);
    } else {
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${actorId}`, style.internalEvent, style.eventType, style.actor);
    logWithColor(event, [
    ['internal', event.type],
    ['event', event.event?.type],
    ['actor', event.actorRef?.id],
    ]);
    }
    break;
    }
    case '@xstate.actor': {
    const actorId = event.actorRef?.id;
    console.groupCollapsed(`%c${internalEvent} %c${actorId}`, style.internalEvent, style.actor);
    logWithColor(event, [
    ['internal', event.type],
    ['actor', actorId],
    ]);
    break;
    }
    case '@xstate.microstep': {
    const actorId = event.actorRef?.id;
    const eventType = event.event?.type;
    const stateValue = getStateValueStrings(event.snapshot.value).sort().join('\n ');
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${actorId}\n %c${stateValue}`, style.internalEvent, style.eventType, style.actor, style.internalEvent);
    logWithColor(event, [
    ['internal', event.type],
    ['event', event.event?.type],
    ['actor', event.actorRef?.id],
    ['internal', getStateNotation(event.snapshot?.value).sort().join('\n ')],
    ]);
    break;
    }
    case '@xstate.action': {
    const actorId = event.actorRef?.id;
    const actionType = event.action?.type;
    if (actionType === 'xstate.emit') {
    const eventType = event.action.params?.event?.type;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${actorId} %c${eventType}`, style.internalEvent, style.action, style.actor, style.eventType);
    } else if (actionType === 'xstate.stopChild' || actionType === 'xstate.spawnChild') {
    const childSrc = event.action.params?.src;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${actorId} ➤ %c${childSrc}`, style.internalEvent, style.action, style.actor, style.actor);
    } else if (actionType === 'xstate.sendTo') {
    const targetId = event.action.params?.to?.id;
    const eventType = event.action.params?.event?.type;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${eventType} %c${actorId} ➤ %c${targetId}`, style.internalEvent, style.action, style.eventType, style.actor, style.actor);
    if (event.action?.type === 'xstate.emit') {
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['event', event.action.params?.event?.type],
    ['actor', event.actorRef?.id],
    ]);
    } else if (event.action?.type === 'xstate.stopChild' || event.action?.type === 'xstate.spawnChild') {
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['actor', event.actorRef?.id],
    ['arrow', ''],
    ['actor', event.action.params?.src],
    ]);
    } else if (event.action?.type === 'xstate.sendTo') {
    logWithColor(event, [
    ['internal', event.type],
    ['action', event.action?.type],
    ['event', event.action.params?.event?.type],
    ['actor', event.actorRef?.id],
    ['arrow', ''],
    ['actor', event.action.params?.to?.id],
    ]);
    }

    // console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${actorId}`, style.internalEvent, style.action, style.actor);
    break;
    }
    }
    }

    function logWithColor(event: InspectionEvent, values: [styleName: Exclude<keyof typeof stylesByName, number | symbol>, value: string][]): void {
    console.groupCollapsed(values.map(([_, value]) => `%c${value}`).join(' '), ...values.map(([styleName]) => stylesByName[styleName]));
    console.log(event);
    console.groupEnd();
    }

    function getStateValueStrings(stateValue: StateValue): string[] {
    function getStateNotation(stateValue: StateValue): string[] {
    if (typeof stateValue === 'string') {
    return [stateValue];
    }
    const valueKeys = Object.keys(stateValue);
    return valueKeys.concat(...valueKeys.map(key => getStateValueStrings(stateValue[key]!).map(s => key + '.' + s)));
    return valueKeys.concat(...valueKeys.map(key => getStateNotation(stateValue[key]!).map(s => `${key}.${s}`)));
    }
    ```
  9. JamieMason revised this gist Jan 23, 2025. 1 changed file with 18 additions and 6 deletions.
    24 changes: 18 additions & 6 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -50,21 +50,33 @@ export function logInspectionEvent(event: InspectionEvent): void {
    break;
    }
    case '@xstate.actor': {
    const subjectId = event.actorRef?.id;
    console.groupCollapsed(`%c${internalEvent} %c${subjectId}`, style.internalEvent, style.actor);
    const actorId = event.actorRef?.id;
    console.groupCollapsed(`%c${internalEvent} %c${actorId}`, style.internalEvent, style.actor);
    break;
    }
    case '@xstate.microstep': {
    const subjectId = event.actorRef?.id;
    const actorId = event.actorRef?.id;
    const eventType = event.event?.type;
    const stateValue = getStateValueStrings(event.snapshot.value).sort().join('\n ');
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${subjectId}\n %c${stateValue}`, style.internalEvent, style.eventType, style.actor, style.internalEvent);
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${actorId}\n %c${stateValue}`, style.internalEvent, style.eventType, style.actor, style.internalEvent);
    break;
    }
    case '@xstate.action': {
    const subjectId = event.actorRef?.id;
    const actorId = event.actorRef?.id;
    const actionType = event.action?.type;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${subjectId}`, style.internalEvent, style.action, style.actor);
    if (actionType === 'xstate.emit') {
    const eventType = event.action.params?.event?.type;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${actorId} %c${eventType}`, style.internalEvent, style.action, style.actor, style.eventType);
    } else if (actionType === 'xstate.stopChild' || actionType === 'xstate.spawnChild') {
    const childSrc = event.action.params?.src;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${actorId} ➤ %c${childSrc}`, style.internalEvent, style.action, style.actor, style.actor);
    } else if (actionType === 'xstate.sendTo') {
    const targetId = event.action.params?.to?.id;
    const eventType = event.action.params?.event?.type;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${eventType} %c${actorId} ➤ %c${targetId}`, style.internalEvent, style.action, style.eventType, style.actor, style.actor);
    }

    // console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${actorId}`, style.internalEvent, style.action, style.actor);
    break;
    }
    }
  10. JamieMason revised this gist Jan 22, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ A small `inspect` function to log events to the browser console, when you expand

    ## Screenshot

    ![Screenshot](https://gist.github.com/user-attachments/assets/f43a343f-5e49-4dbe-9482-dd08d9c6bd76)
    ![Screenshot](https://gist.github.com/user-attachments/assets/29890e7e-04cf-46be-bb41-b43c6184485c)

    ## Usage

  11. JamieMason revised this gist Jan 22, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -43,7 +43,7 @@ export function logInspectionEvent(event: InspectionEvent): void {
    const sourceId = event.sourceRef?.id;
    const actorId = event.actorRef?.id;
    if (sourceId && actorId) {
    console.groupCollapsed(`%c${internalEvent} %c${sourceId} %c${eventType} ➤ %c${actorId}`, style.internalEvent, style.actor, style.eventType, style.actor);
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${sourceId} ➤ ${actorId}`, style.internalEvent, style.eventType, style.actor);
    } else {
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${actorId}`, style.internalEvent, style.eventType, style.actor);
    }
  12. JamieMason revised this gist Jan 22, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -4,7 +4,7 @@ A small `inspect` function to log events to the browser console, when you expand

    ## Screenshot

    ![Screenshot](https://gist.github.com/user-attachments/assets/ac27f7cb-87ea-48da-a0a5-4149b8dc96d3)
    ![Screenshot](https://gist.github.com/user-attachments/assets/f43a343f-5e49-4dbe-9482-dd08d9c6bd76)

    ## Usage

  13. JamieMason revised this gist Jan 22, 2025. 1 changed file with 12 additions and 3 deletions.
    15 changes: 12 additions & 3 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -21,9 +21,9 @@ I know right 🤮, mainly this is down to [the API for applying colours to brows
    If you do any clean up on this, please comment and let me know. You'll notice there is a `ts: Property 'id' does not exist on type 'ActorRefLike'` – but they can have an ID.

    ```ts
    import { type InspectionEvent } from 'xstate';
    import type { InspectionEvent, StateValue } from 'xstate';

    function logInspectionEvent(event: InspectionEvent): void {
    export function logInspectionEvent(event: InspectionEvent): void {
    const internalEvent = event.type;
    const style = {
    action: 'color:#F48FB1',
    @@ -57,7 +57,8 @@ function logInspectionEvent(event: InspectionEvent): void {
    case '@xstate.microstep': {
    const subjectId = event.actorRef?.id;
    const eventType = event.event?.type;
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${subjectId}`, style.internalEvent, style.eventType, style.actor);
    const stateValue = getStateValueStrings(event.snapshot.value).sort().join('\n ');
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${subjectId}\n %c${stateValue}`, style.internalEvent, style.eventType, style.actor, style.internalEvent);
    break;
    }
    case '@xstate.action': {
    @@ -70,4 +71,12 @@ function logInspectionEvent(event: InspectionEvent): void {
    console.log(event);
    console.groupEnd();
    }

    function getStateValueStrings(stateValue: StateValue): string[] {
    if (typeof stateValue === 'string') {
    return [stateValue];
    }
    const valueKeys = Object.keys(stateValue);
    return valueKeys.concat(...valueKeys.map(key => getStateValueStrings(stateValue[key]!).map(s => key + '.' + s)));
    }
    ```
  14. JamieMason revised this gist Jan 22, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@ const [state, send, actor] = useActor(myMachine, {

    I know right 🤮, mainly this is down to [the API for applying colours to browser console logs](https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output) and this is about 30 minutes' work.

    If you do any clean up on this, please comment and let me know. You'll notice there is a `ts: Property 'id' does not exist on type 'ActorRefLike'` – but they do have an ID.
    If you do any clean up on this, please comment and let me know. You'll notice there is a `ts: Property 'id' does not exist on type 'ActorRefLike'` – but they can have an ID.

    ```ts
    import { type InspectionEvent } from 'xstate';
  15. JamieMason revised this gist Jan 22, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@ const [state, send, actor] = useActor(myMachine, {

    I know right 🤮, mainly this is down to [the API for applying colours to browser console logs](https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output) and this is about 30 minutes' work.

    If you do any clean up on this, please comment and let me know.
    If you do any clean up on this, please comment and let me know. You'll notice there is a `ts: Property 'id' does not exist on type 'ActorRefLike'` – but they do have an ID.

    ```ts
    import { type InspectionEvent } from 'xstate';
  16. JamieMason revised this gist Jan 22, 2025. 1 changed file with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,7 @@
    # Log XState inspector events to the Browser Console

    A small `inspect` function to log events to the browser console, when you expand a group you see a log of the full event object.

    ## Screenshot

    ![Screenshot](https://gist.github.com/user-attachments/assets/ac27f7cb-87ea-48da-a0a5-4149b8dc96d3)
  17. JamieMason revised this gist Jan 22, 2025. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,9 @@
    # Log XState inspector events to the Browser Console

    ## Screenshot

    ![Screenshot](https://gist.github.com/user-attachments/assets/ac27f7cb-87ea-48da-a0a5-4149b8dc96d3)

    ## Usage

    ```ts
  18. JamieMason created this gist Jan 22, 2025.
    67 changes: 67 additions & 0 deletions log-x-state-inspector-events-to-the-browser-console.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,67 @@
    # Log XState inspector events to the Browser Console

    ## Usage

    ```ts
    const [state, send, actor] = useActor(myMachine, {
    inspect: logInspectionEvent,
    });
    ```

    ## Implementation

    I know right 🤮, mainly this is down to [the API for applying colours to browser console logs](https://developer.mozilla.org/en-US/docs/Web/API/console#styling_console_output) and this is about 30 minutes' work.

    If you do any clean up on this, please comment and let me know.

    ```ts
    import { type InspectionEvent } from 'xstate';

    function logInspectionEvent(event: InspectionEvent): void {
    const internalEvent = event.type;
    const style = {
    action: 'color:#F48FB1',
    actor: 'color:inherit',
    eventType: 'color:#81D4FA',
    internalEvent: 'color:#999',
    } as const;

    switch (event.type) {
    case '@xstate.snapshot': {
    const actor = event.actorRef?.id;
    console.groupCollapsed(`%c${internalEvent} %c${actor}`, style.internalEvent, style.actor);
    break;
    }
    case '@xstate.event': {
    const eventType = event.event?.type;
    const sourceId = event.sourceRef?.id;
    const actorId = event.actorRef?.id;
    if (sourceId && actorId) {
    console.groupCollapsed(`%c${internalEvent} %c${sourceId} %c${eventType} ➤ %c${actorId}`, style.internalEvent, style.actor, style.eventType, style.actor);
    } else {
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${actorId}`, style.internalEvent, style.eventType, style.actor);
    }
    break;
    }
    case '@xstate.actor': {
    const subjectId = event.actorRef?.id;
    console.groupCollapsed(`%c${internalEvent} %c${subjectId}`, style.internalEvent, style.actor);
    break;
    }
    case '@xstate.microstep': {
    const subjectId = event.actorRef?.id;
    const eventType = event.event?.type;
    console.groupCollapsed(`%c${internalEvent} %c${eventType} %c${subjectId}`, style.internalEvent, style.eventType, style.actor);
    break;
    }
    case '@xstate.action': {
    const subjectId = event.actorRef?.id;
    const actionType = event.action?.type;
    console.groupCollapsed(`%c${internalEvent} %c${actionType} %c${subjectId}`, style.internalEvent, style.action, style.actor);
    break;
    }
    }
    console.log(event);
    console.groupEnd();
    }
    ```