npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

flex-path

v0.1.1-e

Published

Dynamic handler for location / history updates. Inspired by python's args & kwargs system. Includes search query and hash subpath handling. React capable.

Downloads

32

Readme

FlexPath NPM package React (optional)

FlexPath attempts to translate the concept of Python's *args and **kwargs into an alternative URL scheme, with the aim of enhancing functionality and flexibility on SPAs. It manages the history state machine and <a> tag interactions, while allowing for complete control over the current path, arguments and parameters.

Usage

To start using the FlexPath library, you need to - for sure - import it first:

import FlexPath from 'flex-path';

From this point on, each <a>-tag will be routed through the FlexPath system, respecting the target attribute. From now on, you can easily use HTML <a> tags or the FlexPath API to navigate your page. Each modification to the FlexPath state machine will immediately update the URL accordingly.

/*** NAVIGATE SOMEWHERE ***/
FlexPath.args.Update("home", "info");
//   --> /home/info

// This can also be done by path strings...
FlexPath.raw = "/home/info"
// ...or by function call
FlexPath.NavigateTo("/home/info");



/*** UPDATE ARGUMENT 1 ***/
FlexPath.args.Set(1, "about");
//   --> /home/about

/*** UPDATE KWARG FOR SORTING ***/
FlexPath.kwargs.Set("sort", "name");
//   --> /home/about/sort:name

/*** GO TO HOME PAGE ***/
FlexPath.args.Clear();
//   --> /

/*** APPLY MULTIPLE CHANGES BY FREEZING ***/
FlexPath.Freeze();	  // <-- Everything after that won't be immediately applied
FlexPath.args.Update("shop", "items");
FlexPath.kwargs.Set("category", "pets");
FlexPath.kwargs.Set("sort", "relevant");
FlexPath.search.Set("view", "1");
FlexPath.Unfreeze();  // <-- Hereby any changes will be applied at once
//   --> /shop/items/category:pets/sort:relevant?view=1

⚠️ It is highly recommended NOT to use the standard APIs such as history.pushState or location.hash for changing the navigation along this, as they will likely collide with the FlexPath system.

Naming & Scheme

FlexPath deconstructs any URL pathname by the following scheme:

   /arg1/arg2/kwarg1:x/kwarg2:y?query=stuff#hash/can:be?the=same
   |--ARGS---|-----KWARGS------|--SEARCH--|--------HASH---------|
  • ARGS are the "traditional" positional arguments, handled by a positional stack controller. This can be used to dictate the basic structure of the current navigation position within the app.
  • KWARGS are the new stuff. They come in a key:value representation, being a more readable and slightly more beautiful variant of traditional search query params. Kwargs are handled by a keyword stack controller
    • Multiple keyword arguments can be chained together, so paths like /x:12/x:abc are possible.
    • key literals must consist of alphanumeric symbols as well as _ and -. (same goes for search params)
  • SEARCH is nothing new, just good old search queries. These fields are also handled by a keyword stack controller
  • HASH represents an entirely seperate subsystem here. While the hash can still be used to e.g. navigate to ID anchors, FlexPath allows for a completely seperate "sub path" to be stored within the hash, itself again consisting of ARGS, KWARGS and SEARCH.

The order is important, so KWARGS cannot precede ARGS. Multiple slashes will be ignored, so //slash////path won't result in multiple empty arguments. The basic idea is to go from this

https://my-webpage.com/shop/items?category=523&filter=shower%20heads&sort=recent

to this

https://my-webpage.com/shop/items/category:523/filter:shower+heads/sort:recent

while still maintaining the ability to extend to search params and even the hash part if really needed. But the ultimate goal should be to prevent bloated URLs such as the above. Ideally, you should only be in need of ARGS and KWARGS.

Handling events

To watch for any kind of path changes, FlexPath comes with a handful events to watch for updates in the path state machine. Events are provided by the stack controllers, allowing precise listening and flexible handling (see the API Reference for more detailed information).

function handleArg0Update(newArg: string | undefined) {
	// Do something with the new argument value
}

function handleSearchUpdate(newValues: string[]) {
	// keyword argument updates always provide an array
	// due to their ability of containing multiple values
	// for the same key
}

FlexPath.args.OnUpdate(0, handleArg0Update);
FlexPath.kwargs.OnUpdate("search", handleSearchUpdate);


// When you don't wish to receive updates anymore, simply unregister the event
FlexPath.args.OffUpdate(0, handleArg0Update);

React Integration

To integrate with the React framework more easily, FlexPath optionally provides a set of hooks as well as an abstract class component derivate to simplify path state updates.

Hooks

import { useFpArg, useFpKwargs } from 'flex-path/react'

function AwesomePathReactiveComponent() {
	const mainPage = useFpArg(0);
	const [search] = useFpKwargs("search")

	return <></>
}
  • useFpArgs(): string[]Entire positional argument stack
  • useFpArg(index: number): string | undefinedSpecific positional argument
  • useFpKwargs(key: string): string[]Keyword argument values
  • useFpSearch(key: string): string[]Search param values
  • useFpHashArgs(): string[]Entire positional argument stack on hash subpath
  • useFpHashArg(index: number): string | undefinedSpecific positional argument on hash subpath
  • useFpHashKwargs(key: string): string[]Keyword argument values on hash subpath
  • useFpHashSearch(key: string): string[]Search param values on hash subpath

Class Components

The FlexPathComponent class inherits directly from React's very own Component, so the core functionalities are the same. Other than that, a FlexPathComponent comes with a built-in listener management, so any registered listeners and handlers will only be active after mounting and the initial render call, and are safely unregistered once the component unmounts.

import { FlexPathComponent } from 'flex-path/react'

class AwesomePathReactiveComponent extends FlexPathComponent {
	constructor(props) {
		super(props);
		this.addFpArgListener(0, (newArg) => {...});
		
		// Listeners can also be added without a handler attached.
		// In this case, the event will only re-render the component.
		this.addFpKwargsListener("search");
	}
	
	render() {
		...
	}
}

Any handlers provided to the listener will receive the respective value types described as return types in the Hooks section. From within the class accessible methods are:

  • addFpArgsListener(handler?, skipRendering?)Entire positional argument stack
  • addFpArgListener(index: number, handler?, skipRendering?)Specific positional argument
  • addFpKwargsListener(key: string, handler?, skipRendering?)Keyword argument values
  • addFpSearchListener(key: string, handler?, skipRendering?)Search param values
  • addFpHashArgsListener(handler?, skipRendering?)Entire positional argument stack on hash subpath
  • addFpHashArgListener(index: number, handler?, skipRendering?)Specific positional argument on hash subpath
  • addFpHashKwargsListener(key: string, handler?, skipRendering?)Keyword argument values on hash subpath
  • addFpHashSearchListener(key: string, handler?, skipRendering?)Search param values on hash subpath

To prevent further bloatage, no more state storing is done by the class component. Instead, it simply forces a rerender once one of the active listeners is invoked. If you with to handle the values and updates with reacts built-in states instead of manually fetching the values from the FlexPath controller, you need to enable skipRendering and implement this functionality yourself.

Background & Motivation

Dealing with the traditional URL scheme is fine and all, but with all the different layers, levels, subsystems, and complex structures that an SPA can take on, it can be pretty tough to navigate using classic linear-parametric URL path arguments. Not only can it be a hassle to worry about reliable and comprehensive handling of history events and link clicks, but the linear /one/argument/chase/that/other structure can also get pretty tedious. While it does a great job of representing a directory-like structure (that's where it comes from after all), it doesn't reflect the complexity of SPAs that is sometimes required in an equally useful way.

Search queries should definitely attempt to solve this problem, but they now raise a more aesthetic problem. Sending and receiving completely overloaded links in the style of https://my-page.org/path/to/shop?view=article&filter=advanced%20socks%20with%20ears&orderby=name&ordermd=desc immediately deconstructs any intentions of even trying to understand what the heck is going on there. This method cannot be called visually appealing.

So with that and Python's *args and **kwargs parameter system in mind, FlexPath attempts to union the traditional URL path scheme with a more sleak, modular approach.

API Reference

| |Method Badges| |-|-| |🔤| Case SensitivityMethod describes a case insensitive version, but a case sensitive variant exists.Usually these variants are suffixed by Case. | |⚡| Event RelatedMethod interacts with the event system.See lux-callback-emitter for details about handler registration schemes. | |🔰| Test MethodServes as a verificator. Throws an error if the test did not pass. |

FlexPath Base

Parameters

||Type|Description| |-|-|-| | .args | PositionalStackController | Controller for any positional arguments in the main URL path. | | .kwargs | KeywordStackController | Controller for all keyword arguments in the main URL path. | | .search | KeywordStackController | Controller for all search query values in the main URL path. | | .hashArgs | PositionalStackController | Controller for any positional arguments in the hash URL path. | | .hashKwargs | KeywordStackController | Controller for all keyword arguments in the hash URL path. | | .hashSearch | KeywordStackController | Controller for all search query values in the hash URL path. | | .raw | string | Raw and full path, including search queries and hash.Updating this will navigate towards it. If you supply an external path, it will be opened in a new tab. | | .stateMode | HistoryCallMode | The global history state machine mode.Defaults to push. |

.NavigateTo(path)

Fetches the positional argument value at the given index, if existing. This version is case insensitive, always returning lowercase. Returns: string. |Parameter|Description| |-|-| |path : string | URL| URL or path literal to navigate to |

.NavigateTo(path, target)

Same as above, but includes target specification |Parameter|Description| |-|-| |path : string | URL | URL or path literal to navigate to | |target : string | Href target |

.NavigateTo(path, newTab)

Same as above, but allows easy "new tab"-behaviour by targeting to _blank if target is set to true |Parameter|Description| |-|-| |path : string | URL | URL or path literal to navigate to | |newTab : boolean | Whether to open the path in a new tab |

.Freeze()

Freezes the history state machine. This allows multiple updates without invoking a history state call each time. |Parameter|Description| |-|-| |path : string | URL| URL or path literal to navigate to |

NOTICE: Remember to Unfreeze() the engine again once you're done!

.Unfreeze(mode?)

Unfreezes the history state machine and applys all intermediate updates. |Parameter|Description| |-|-| |mode? : HistoryCallMode| Defaults to FlexPath.stateModeOverride the current global state mode for this unfreeze action |

.UseExternalUrlMiddleware(middleware)

Adds a custom middleware for any navigation events attempting to open an external URL. |Parameter|Description| |-|-| |middleware : ExternalUrlMiddleware| Middleware to validate the navigation process |

Positional Stack Controller

Indexing

Positional stack controllers support direct numeric indexing, just like arrays. This is equivalent to the use of .Get and .Set. The following example displays the analogy of indexing and method usage:

const value = FlexPath.args[0];
//          = FlexPath.args.Get(0);

FlexPath.args[0] = "value";
//      .args.Set(0, "value");

.GetAll()

Fetches a complete array of all positional path arguments. Returns: string[].

.Get(index) 🔤

Fetches the positional argument value at the given index, if existing. This version is case insensitive, always returning lowercase. Returns: string. |Parameter|Description| |-|-| |index : number| Path position index |

This method implements a case sensitive alternative: .GetCase(index)

.Set(index, argument, keepKwargs?, keepSearch?) 🔤

Updates the positional argument value at the given index. This is possible for any index from 0 to length, which allows updating the "next empty argument".This version is case insensitive, always updating to lowercase values. |Parameter|Description| |-|-| |index : number| Path position index | |argument : string| Value to insert into the path structure | |keepKwargs? : boolean| Defaults to false.Whether this call should keep the current kwargs values. | |keepSearch? : boolean| Defaults to false.Whether this call should keep the current search query. |

This method implements a case sensitive alternative: .SetCase(index, argument, keepKwargs?, keepSearch?)

.Clear(keepKwargs?, keepSearch?)

Clears the current positional argument stack, essentially navigating to /. |Parameter|Description| |-|-| |keepKwargs? : boolean| Defaults to false.Whether this call should keep the current kwargs values. | |keepSearch? : boolean| Defaults to false.Whether this call should keep the current search query. |

.Pop()

Pops and returns the rightmost argument value from the positional argument stack. Returns: string.

.Pop(amount)

Pops and returns the amount rightmost argument values. Returns: string[]. |Parameter|Description| |-|-| |amount : number| Amount of arguments to pop from the positional argument stack |

.Push(...arguments) 🔤

Pushes (inserts) one or more values to the positional argument stack. |Parameter|Description| |-|-| |...arguments : string[]| Argument values to push |

This method implements a case sensitive alternative: .PushCase(...arguments)

.Update(...arguments) 🔤

Updates the entire positional argument stack as a whole, essentially "navigating the path". |Parameter|Description| |-|-| |...arguments : string[]| Argument values to store |

This method implements a case sensitive alternative: .UpdateCase(...arguments)

🔰 .ValidateLength(expectedLength)

Tests the current stack for the expected length.Throws otherwise. |Parameter|Description| |-|-| |expectedLength : number| Number of expected arguments in the stack |

🔰 .ValidateIndex(index, targetValue) 🔤

Tests the argument at the provided index.Throws otherwise. |Parameter|Description| |-|-| |index : number| Stack index to test | |targetValue : string| Expected value (case insensitive) |

This method implements a case sensitive alternative: .ValidateIndexCase(index, targetValue)

🔰 .ValidatePath(...args) 🔤

Tests the current stack to match the test pattern.Throws otherwise. |Parameter|Description| |-|-| |...arguments : (string|false)[]| Test pattern fields.false will pass any value at the respective position (except undefined). |

This method implements a case sensitive alternative: .ValidatePathCase(index, targetValue)

.OnUpdate(handler)

Unregisters a handler function for functional argument update at specified position. This will call for any argument update. |Parameter|Description| |-|-| |handler : PathUpdateEventHandler| Event handler function |

.OnUpdate(index, handler)

Registers a handler function for functional argument path updates.Event values will always be returned case sensitive |Parameter|Description| |-|-| |index : number| Index to register the handler for | |handler : ArgUpdateEventHandler| Event handler function |

.OffUpdate(handler) / .OffUpdate(index, handler)

Unregisters provided handler function if registered. Signatures are identical to .OnUpdate.

.OnceUpdate(handler) / .OnceUpdate(index, handler)

Similar to .OnUpdate equivalents, but registers the provided handler only once, being unregistered right after invocation. Signatures are identical to .OnUpdate.

.GetUpdateListener(handler) / .GetUpdateListener(index, handler)

Similar to .OnUpdate equivalents, but creates and returns a new CallbackEmitterListener. Signatures are identical to .OnUpdate.

Keyword Stack Controller

Indexing

Keyword stack controllers support direct string literal indexing, just like objects. This is equivalent to the use of .Get and .Set. The following example displays the analogy of indexing and method usage:

const value = FlexPath.args["sort"];
//          = FlexPath.args.Get("sort");

FlexPath.args["sort"] = "value";
//      .args.Set("sort", "value");

.Get(key) 🔤

Fetches first value for the provided key, if existing. This version is case insensitive, always returning lowercase. Returns: string | undefined. |Parameter|Description| |-|-| |key : string| Key literal |

This method implements a case sensitive alternative: .GetCase(key)

.GetAll(key) 🔤

Fetches all values for the provided key. This version is case insensitive, always returning lowercase. Returns: string[]. |Parameter|Description| |-|-| |key : string| Key literal |

This method implements a case sensitive alternative: .GetAllCase(key)

.Set(key, values) 🔤

Sets the value(s) for a given key literal. This is overloaded for either working with one string[] argument or multiple string arguments, replacing values with ...values in this case. This version is case insensitive, always inserting lowercase values. |Parameter|Description| |-|-| |key : string| Key literal | |values : string[]| Value(s) to apply|

This method implements a case sensitive alternative: .SetCase(key, values)

.Add(key, ...values) 🔤

Works similar to .Set, but adds the provided values to the key literal instead of replacing them. Duplicate values will be filtered.This version is case insensitive, always adding lowercase values. |Parameter|Description| |-|-| |key : string| Key literal | |...values : string[]| Value(s) to add|

This method implements a case sensitive alternative: .AddCase(key, ...values)

.Delete(key, ...values)

Inversion of .Add, thus removing provided value(s) from the key literal.This call is always case sensitive |Parameter|Description| |-|-| |key : string| Key literal | |...values : string[]| Value(s) to remove |

.Delete(key)

Radical edition of the above, removing the entire key literal and all of it's values from the controller. |Parameter|Description| |-|-| |key : string| Key literal |

.Clear()

Clears all fields of this container.

🔰 .ValidateKey(key)

Validates the existance of any values on the provided key literal.Throws otherwise. |Parameter|Description| |-|-| |key : string| Key literal to validate |

🔰 .ValidateValues(key, ...values) 🔤

Validates the existence of all provided values on the provided key literal.This version is case insensitive, always comparing lowercase values.Throws otherwise. |Parameter|Description| |-|-| |key : string| Key literal to look up | |...values : string[]| Values to validate |

This method implements a case sensitive alternative: .ValidateValuesCase(key, ...values)

.OnUpdate(key, handler)

Registers a handler function for value changes on the provided key.Event values will always be returned case sensitive |Parameter|Description| |-|-| |key : string| Key literal | |handler : KwargUpdateEventHandler| Event handler function |

.OffUpdate(key, handler)

Unregisters provided handler function if registered. Signatures are identical to .OnUpdate.

.OnceUpdate(key, handler)

Similar to .OnUpdate equivalents, but registers the provided handler only once, being unregistered right after invocation. Signatures are identical to .OnUpdate.

.GetUpdateListener(key, handler)

Similar to .OnUpdate equivalents, but creates and returns a new CallbackEmitterListener. Signatures are identical to .OnUpdate.

Types

HistoryCallMode

|Literal|Description| |-|-| |"push"| Pushes a new state to the browser history(default) | |"update"| Updates the current browser state |

ExternalUrlMiddleware

A custom function receiving the following arguments: |Argument|Description| |-|-| |url : URL| The external URL the state machine attempts to navigate to | |acceptNavigation : EUM$Resolver| Updates the current browser state |

EUM$Resolver

A function provided to ExternalUrlMiddleware with the following parameters |Argument|Description| |-|-| |accepted : boolean | Whether the navigation event should be accepted | |options? : EUM$ResolverOptions| Updates the current browser state |

(accepted: boolean, options?: ExternalUrlOptions) => void

EUM$ResolverOptions

A partial object that can be passed to EUM$Resolver consisting of following fields: |Field|Description| |-|-| |newTab? : boolean | Whether the navigation event should be done in a new tab | |target? : string| The target to apply to the navigation event |

PathUpdateEventHandler

(newArg: string | undefined) =>  void;

ArgUpdateEventHandler

(...args: string[]) =>  void;

KwargUpdateEventHandler

(newValues: string[]) =>  void;

Patch Notes

V0.1.1

  • Fixed an issue that caused Vite and rollup to bundle some dependencies in the wrong order, resulting in undefined constructors.
  • Singleton class instance will now be initialized at the first getter call by the FlexPath proxy.

V0.1.0

  • Nothing changed yet, because this is the first version. Yayy!

🗒️ TODOs

  • Implement logical BACK functionality to e.g. navigate "back to the menu" without the need of manually keeping track of anything
  • Optimize quick access usage