diff --git a/.eslintrc.js b/.eslintrc.js index 9b45df1..2ca0f66 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,3 +1,7 @@ module.exports = { - extends: ['eslint-config-kouts/vue2'] + extends: ['eslint-config-kouts/vue2'], + rules: { + 'max-len': 0, + 'prettier/prettier': 0 + } } diff --git a/CHANGELOG.md b/CHANGELOG.md index b4e36a1..837b95c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [2.1.0](https://github.com/kouts/vue-path-store/compare/v2.0.1...v2.1.0) (2021-05-25) + + +### Features + +* added pinia plugin ([def484c](https://github.com/kouts/vue-path-store/commit/def484c3facea9640727759660e79420ee2d20e1)) + ## [2.0.1](https://github.com/kouts/vue-path-store/compare/v2.0.0...v2.0.1) (2021-05-17) diff --git a/README.md b/README.md index 730767c..dfc66c7 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,30 @@ # vue-path-store ![](https://img.badgesize.io/kouts/vue-path-store/main/dist/umd/pathStore.min.js.svg) ![](https://img.badgesize.io/kouts/vue-path-store/main/dist/umd/pathStore.min.js.svg?compression=gzip) -`vue-path-store` is a lightweight shared state management solution for Vue. -At it's heart lays a simple reactive store, that uses the dot notation path syntax for data mutation. +**PathStore** is a lightweight **shared** state management solution for Vue. +At its heart lays a simple reactive store, which uses the dot notation path syntax for data mutation. -It also comes with a Vuex plugin flavor so that you can use the dot notation to mutate state in Vuex. +It also comes with: +- a **[Vuex](https://vuex.vuejs.org/) plugin** so that you can use dot notation along with the +full power of Vuex (actions, getters, modules, devtools) as well. -## Install -```sh -npm install vue-path-store -``` +- a **[Pinia](https://pinia.esm.dev/) plugin** so that you can use dot notation with your favorite fruity store. -## Example use of standalone - -```js -// main.js -import Vue from 'vue' -import App from './App.vue' -import { createVuePathStore } from 'vue-path-store' - -// Initialize pathStore -const store = createPathStore({ - message: 'Initial message' -}) - -// Provide pathStore to all Vue components -Vue.prototype.$s = store - -new Vue({ - render: (h) => h(App) -}).$mount('#app') -``` - -```html - - -``` - -## Example use of Vuex plugin - -```js -// main.js -import Vue from 'vue' -import Vuex from 'vuex' -import App from './App.vue' -import { pathStoreVuexPlugin } from 'vue-path-store/dist/es/pathStoreVuexPlugin' - -// Initialize Vuex with the pathStoreVuexPlugin -const store = createPathStore({ - plugins: [pathStoreVuexPlugin] - state: { - message: 'Initial message' - } -}) - -new Vue({ - store, - render: (h) => h(App) -}).$mount('#app') -``` - -```html - - -``` +## Features +- Share state easily between components using either the object or composition API +- Use dot (or bracket) notation for mutating state (set, delete) +- Creates intermediate reactive object/array structures if not available while setting state +- Avoid Vue [change detection caveats](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats) +- Extra methods for `Array` manipulation +- Enhance Vuex with dot notation by utilizing the PathStore Vuex Plugin +- Enhance Pinia with dot notation by utilizing the PathStore Pinia Plugin ## Browsers support | [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | | --------- | --------- | --------- | --------- | --------- | -| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions \ No newline at end of file +| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions + + +Click here for documentation and examples +https://vue-path-store.netlify.app/ \ No newline at end of file diff --git a/dist/cjs/pathStore.js b/dist/cjs/pathStore.js index 094be99..99a088f 100644 --- a/dist/cjs/pathStore.js +++ b/dist/cjs/pathStore.js @@ -194,21 +194,19 @@ var deleteMany = function deleteMany(obj, path) { var ARRAY_METHODS = ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']; -var createPathStore = function createPathStore(state) { - var store = Vue__default['default'].observable(state); - - var methods = _objectSpread2({ +function createPathStoreMethods() { + return _objectSpread2({ set: function set(path, value) { - setMany(store, path, value); + setMany(this, path, value); }, toggle: function toggle(path) { - setOne(store, path, !getByPath(store, path)); + setOne(this, path, !getByPath(this, path)); }, get: function get(path) { - return path ? getByPath(store, path) : store; + return path ? getByPath(this, path) : this; }, del: function del(path) { - deleteMany(store, path); + deleteMany(this, path); } }, ARRAY_METHODS.reduce(function (acc, method) { var fn = function fn() { @@ -217,19 +215,21 @@ var createPathStore = function createPathStore(state) { } var path = args.shift(); - var arr = getByPath(store, path); + var arr = getByPath(this, path); if (!isArray(arr)) { throw Error('Argument must be an array.'); } - arr[method].apply(arr, args); + return arr[method].apply(arr, args); }; return Object.assign(acc, _defineProperty({}, method, fn)); }, {})); +} - return Object.assign(store, methods); +var createPathStore = function createPathStore(state) { + return Object.assign(Vue__default['default'].observable(state), createPathStoreMethods()); }; exports.createPathStore = createPathStore; diff --git a/dist/cjs/pathStorePiniaPlugin.js b/dist/cjs/pathStorePiniaPlugin.js new file mode 100644 index 0000000..dea2afc --- /dev/null +++ b/dist/cjs/pathStorePiniaPlugin.js @@ -0,0 +1,235 @@ +'use strict'; + +Object.defineProperty(exports, '__esModule', { value: true }); + +var Vue = require('vue'); + +function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } + +var Vue__default = /*#__PURE__*/_interopDefaultLegacy(Vue); + +function ownKeys(object, enumerableOnly) { + var keys = Object.keys(object); + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(object); + + if (enumerableOnly) { + symbols = symbols.filter(function (sym) { + return Object.getOwnPropertyDescriptor(object, sym).enumerable; + }); + } + + keys.push.apply(keys, symbols); + } + + return keys; +} + +function _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + + if (i % 2) { + ownKeys(Object(source), true).forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + + return target; +} + +function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; +} + +function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} + +function isObject(obj) { + return _typeof(obj) === 'object' && !Array.isArray(obj) && obj !== null; +} + +function isNumeric(str) { + return !isNaN(str) && !isNaN(parseFloat(str)); +} + +function isArray(arr) { + return Array.isArray(arr); +} + +function splitPath(str) { + var regex = /(\w+)|\[([^\]]+)\]/g; + var result = []; + var path; + + while (path = regex.exec(str || '')) { + if (str[path.index] === '[') { + result.push(path[2]); + } else { + result.push(path[1]); + } + } + + return result; +} + +function getByPath(obj, path) { + var parts = isArray(path) ? path : splitPath(path); + var length = parts.length; + + for (var i = 0; i < length; i++) { + if (typeof obj[parts[i]] === 'undefined') { + return undefined; + } + + obj = obj[parts[i]]; + } + + return obj; +} + +var setOne = function setOne(obj, pathStr, value) { + var path = splitPath(pathStr); + var length = path.length; + var lastIndex = length - 1; + + for (var index = 0; index < length; index++) { + var prop = path[index]; // If we are not on the last index + // we start building the data object from the path + + if (index !== lastIndex) { + var objValue = obj[prop]; // If objValue exists, is not primitive and is not observable, then make it so using Vue.set + + if (objValue && _typeof(objValue) === 'object') { + // eslint-disable-next-line no-prototype-builtins + if (!objValue.hasOwnProperty('__ob__')) { + Vue__default['default'].set(obj, prop, objValue); + } // Array to object transformation + // Check if parent path is an array, we are not on the last item + // and the next key in the path is not a number + + + if (isArray(objValue) && !isNumeric(path[index + 1])) { + Vue__default['default'].set(obj, prop, {}); + } + } else { + // Create an empty object or an empty array based on the next path entry + if (isNumeric(path[index + 1])) { + Vue__default['default'].set(obj, prop, []); + } else { + Vue__default['default'].set(obj, prop, {}); + } + } + } else { + // If we are on the last index then we just assign the the value to the data object + // Note: If we used obj[prop] = value; arrays wouldn't be updated. + Vue__default['default'].set(obj, prop, value); + } + + obj = obj[prop]; + } +}; + +var setMany = function setMany(obj, path, value) { + if (typeof path === 'string') { + setOne(obj, path, value); + } else if (isObject(path)) { + for (var key in path) { + setOne(obj, key, path[key]); + } + } else { + throw Error('Arguments must be either string or object.'); + } +}; + +var deleteOne = function deleteOne(obj, pathStr) { + var path = splitPath(pathStr); + var prop = path.pop(); + Vue__default['default']["delete"](getByPath(obj, path), prop); +}; + +var deleteMany = function deleteMany(obj, path) { + if (typeof path === 'string') { + deleteOne(obj, path); + } else if (isArray(path)) { + path.forEach(function (item) { + deleteOne(obj, item); + }); + } else { + throw Error('Arguments must be either string or array.'); + } +}; + +var ARRAY_METHODS = ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']; + +function createPathStoreMethods() { + return _objectSpread2({ + set: function set(path, value) { + setMany(this, path, value); + }, + toggle: function toggle(path) { + setOne(this, path, !getByPath(this, path)); + }, + get: function get(path) { + return path ? getByPath(this, path) : this; + }, + del: function del(path) { + deleteMany(this, path); + } + }, ARRAY_METHODS.reduce(function (acc, method) { + var fn = function fn() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var path = args.shift(); + var arr = getByPath(this, path); + + if (!isArray(arr)) { + throw Error('Argument must be an array.'); + } + + return arr[method].apply(arr, args); + }; + + return Object.assign(acc, _defineProperty({}, method, fn)); + }, {})); +} + +var pathStorePiniaPlugin = function pathStorePiniaPlugin(ctx) { + return Object.assign(ctx.store.actions = ctx.store.actions || {}, createPathStoreMethods()); +}; + +exports.pathStorePiniaPlugin = pathStorePiniaPlugin; diff --git a/dist/cjs/pathStoreVuexPlugin.js b/dist/cjs/pathStoreVuexPlugin.js index b1c4a6a..bbe74e8 100644 --- a/dist/cjs/pathStoreVuexPlugin.js +++ b/dist/cjs/pathStoreVuexPlugin.js @@ -255,14 +255,14 @@ var pathStoreVuexPlugin = function pathStoreVuexPlugin(store) { } var path = args.shift(); - store.commit(method, { + return store.commit(method, { path: path, args: args }); }; return Object.assign(acc, _defineProperty({}, method, fn)); - })); + }, {})); var mutations = _objectSpread2({ set: function set(state, info) { @@ -288,11 +288,11 @@ var pathStoreVuexPlugin = function pathStoreVuexPlugin(store) { throw Error('Argument must be an array'); } - arr[method].apply(arr, _toConsumableArray(args)); + return arr[method].apply(arr, _toConsumableArray(args)); }; return Object.assign(acc, _defineProperty({}, method, fn)); - })); + }, {})); var _loop = function _loop(type) { var entry = store._mutations[type] || (store._mutations[type] = []); diff --git a/dist/es/pathStore.js b/dist/es/pathStore.js index e182a63..a64c662 100644 --- a/dist/es/pathStore.js +++ b/dist/es/pathStore.js @@ -186,21 +186,19 @@ var deleteMany = function deleteMany(obj, path) { var ARRAY_METHODS = ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']; -var createPathStore = function createPathStore(state) { - var store = Vue.observable(state); - - var methods = _objectSpread2({ +function createPathStoreMethods() { + return _objectSpread2({ set: function set(path, value) { - setMany(store, path, value); + setMany(this, path, value); }, toggle: function toggle(path) { - setOne(store, path, !getByPath(store, path)); + setOne(this, path, !getByPath(this, path)); }, get: function get(path) { - return path ? getByPath(store, path) : store; + return path ? getByPath(this, path) : this; }, del: function del(path) { - deleteMany(store, path); + deleteMany(this, path); } }, ARRAY_METHODS.reduce(function (acc, method) { var fn = function fn() { @@ -209,19 +207,21 @@ var createPathStore = function createPathStore(state) { } var path = args.shift(); - var arr = getByPath(store, path); + var arr = getByPath(this, path); if (!isArray(arr)) { throw Error('Argument must be an array.'); } - arr[method].apply(arr, args); + return arr[method].apply(arr, args); }; return Object.assign(acc, _defineProperty({}, method, fn)); }, {})); +} - return Object.assign(store, methods); +var createPathStore = function createPathStore(state) { + return Object.assign(Vue.observable(state), createPathStoreMethods()); }; export { createPathStore }; diff --git a/dist/es/pathStorePiniaPlugin.js b/dist/es/pathStorePiniaPlugin.js new file mode 100644 index 0000000..cf68f94 --- /dev/null +++ b/dist/es/pathStorePiniaPlugin.js @@ -0,0 +1,227 @@ +import Vue from 'vue'; + +function ownKeys(object, enumerableOnly) { + var keys = Object.keys(object); + + if (Object.getOwnPropertySymbols) { + var symbols = Object.getOwnPropertySymbols(object); + + if (enumerableOnly) { + symbols = symbols.filter(function (sym) { + return Object.getOwnPropertyDescriptor(object, sym).enumerable; + }); + } + + keys.push.apply(keys, symbols); + } + + return keys; +} + +function _objectSpread2(target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i] != null ? arguments[i] : {}; + + if (i % 2) { + ownKeys(Object(source), true).forEach(function (key) { + _defineProperty(target, key, source[key]); + }); + } else if (Object.getOwnPropertyDescriptors) { + Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); + } else { + ownKeys(Object(source)).forEach(function (key) { + Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); + }); + } + } + + return target; +} + +function _defineProperty(obj, key, value) { + if (key in obj) { + Object.defineProperty(obj, key, { + value: value, + enumerable: true, + configurable: true, + writable: true + }); + } else { + obj[key] = value; + } + + return obj; +} + +function _typeof(obj) { + "@babel/helpers - typeof"; + + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function _typeof(obj) { + return typeof obj; + }; + } else { + _typeof = function _typeof(obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }; + } + + return _typeof(obj); +} + +function isObject(obj) { + return _typeof(obj) === 'object' && !Array.isArray(obj) && obj !== null; +} + +function isNumeric(str) { + return !isNaN(str) && !isNaN(parseFloat(str)); +} + +function isArray(arr) { + return Array.isArray(arr); +} + +function splitPath(str) { + var regex = /(\w+)|\[([^\]]+)\]/g; + var result = []; + var path; + + while (path = regex.exec(str || '')) { + if (str[path.index] === '[') { + result.push(path[2]); + } else { + result.push(path[1]); + } + } + + return result; +} + +function getByPath(obj, path) { + var parts = isArray(path) ? path : splitPath(path); + var length = parts.length; + + for (var i = 0; i < length; i++) { + if (typeof obj[parts[i]] === 'undefined') { + return undefined; + } + + obj = obj[parts[i]]; + } + + return obj; +} + +var setOne = function setOne(obj, pathStr, value) { + var path = splitPath(pathStr); + var length = path.length; + var lastIndex = length - 1; + + for (var index = 0; index < length; index++) { + var prop = path[index]; // If we are not on the last index + // we start building the data object from the path + + if (index !== lastIndex) { + var objValue = obj[prop]; // If objValue exists, is not primitive and is not observable, then make it so using Vue.set + + if (objValue && _typeof(objValue) === 'object') { + // eslint-disable-next-line no-prototype-builtins + if (!objValue.hasOwnProperty('__ob__')) { + Vue.set(obj, prop, objValue); + } // Array to object transformation + // Check if parent path is an array, we are not on the last item + // and the next key in the path is not a number + + + if (isArray(objValue) && !isNumeric(path[index + 1])) { + Vue.set(obj, prop, {}); + } + } else { + // Create an empty object or an empty array based on the next path entry + if (isNumeric(path[index + 1])) { + Vue.set(obj, prop, []); + } else { + Vue.set(obj, prop, {}); + } + } + } else { + // If we are on the last index then we just assign the the value to the data object + // Note: If we used obj[prop] = value; arrays wouldn't be updated. + Vue.set(obj, prop, value); + } + + obj = obj[prop]; + } +}; + +var setMany = function setMany(obj, path, value) { + if (typeof path === 'string') { + setOne(obj, path, value); + } else if (isObject(path)) { + for (var key in path) { + setOne(obj, key, path[key]); + } + } else { + throw Error('Arguments must be either string or object.'); + } +}; + +var deleteOne = function deleteOne(obj, pathStr) { + var path = splitPath(pathStr); + var prop = path.pop(); + Vue["delete"](getByPath(obj, path), prop); +}; + +var deleteMany = function deleteMany(obj, path) { + if (typeof path === 'string') { + deleteOne(obj, path); + } else if (isArray(path)) { + path.forEach(function (item) { + deleteOne(obj, item); + }); + } else { + throw Error('Arguments must be either string or array.'); + } +}; + +var ARRAY_METHODS = ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']; + +function createPathStoreMethods() { + return _objectSpread2({ + set: function set(path, value) { + setMany(this, path, value); + }, + toggle: function toggle(path) { + setOne(this, path, !getByPath(this, path)); + }, + get: function get(path) { + return path ? getByPath(this, path) : this; + }, + del: function del(path) { + deleteMany(this, path); + } + }, ARRAY_METHODS.reduce(function (acc, method) { + var fn = function fn() { + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + + var path = args.shift(); + var arr = getByPath(this, path); + + if (!isArray(arr)) { + throw Error('Argument must be an array.'); + } + + return arr[method].apply(arr, args); + }; + + return Object.assign(acc, _defineProperty({}, method, fn)); + }, {})); +} + +var pathStorePiniaPlugin = function pathStorePiniaPlugin(ctx) { + return Object.assign(ctx.store.actions = ctx.store.actions || {}, createPathStoreMethods()); +}; + +export { pathStorePiniaPlugin }; diff --git a/dist/es/pathStoreVuexPlugin.js b/dist/es/pathStoreVuexPlugin.js index 97a9147..8b718d1 100644 --- a/dist/es/pathStoreVuexPlugin.js +++ b/dist/es/pathStoreVuexPlugin.js @@ -247,14 +247,14 @@ var pathStoreVuexPlugin = function pathStoreVuexPlugin(store) { } var path = args.shift(); - store.commit(method, { + return store.commit(method, { path: path, args: args }); }; return Object.assign(acc, _defineProperty({}, method, fn)); - })); + }, {})); var mutations = _objectSpread2({ set: function set(state, info) { @@ -280,11 +280,11 @@ var pathStoreVuexPlugin = function pathStoreVuexPlugin(store) { throw Error('Argument must be an array'); } - arr[method].apply(arr, _toConsumableArray(args)); + return arr[method].apply(arr, _toConsumableArray(args)); }; return Object.assign(acc, _defineProperty({}, method, fn)); - })); + }, {})); var _loop = function _loop(type) { var entry = store._mutations[type] || (store._mutations[type] = []); diff --git a/dist/umd/pathStore.js b/dist/umd/pathStore.js index 6347c33..71e8904 100644 --- a/dist/umd/pathStore.js +++ b/dist/umd/pathStore.js @@ -194,21 +194,19 @@ var ARRAY_METHODS = ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift']; - var createPathStore = function createPathStore(state) { - var store = Vue__default['default'].observable(state); - - var methods = _objectSpread2({ + function createPathStoreMethods() { + return _objectSpread2({ set: function set(path, value) { - setMany(store, path, value); + setMany(this, path, value); }, toggle: function toggle(path) { - setOne(store, path, !getByPath(store, path)); + setOne(this, path, !getByPath(this, path)); }, get: function get(path) { - return path ? getByPath(store, path) : store; + return path ? getByPath(this, path) : this; }, del: function del(path) { - deleteMany(store, path); + deleteMany(this, path); } }, ARRAY_METHODS.reduce(function (acc, method) { var fn = function fn() { @@ -217,19 +215,21 @@ } var path = args.shift(); - var arr = getByPath(store, path); + var arr = getByPath(this, path); if (!isArray(arr)) { throw Error('Argument must be an array.'); } - arr[method].apply(arr, args); + return arr[method].apply(arr, args); }; return Object.assign(acc, _defineProperty({}, method, fn)); }, {})); + } - return Object.assign(store, methods); + var createPathStore = function createPathStore(state) { + return Object.assign(Vue__default['default'].observable(state), createPathStoreMethods()); }; exports.createPathStore = createPathStore; diff --git a/dist/umd/pathStore.min.js b/dist/umd/pathStore.min.js index e5cb1e5..b8d4b8b 100644 --- a/dist/umd/pathStore.min.js +++ b/dist/umd/pathStore.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("vue")):"function"==typeof define&&define.amd?define(["exports","vue"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).pathStore={},e.Vue)}(this,(function(e,t){"use strict";function r(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=r(t);function o(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function u(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function f(e){return(f="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function i(e){return!isNaN(e)&&!isNaN(parseFloat(e))}function c(e){return Array.isArray(e)}function s(e){for(var t,r=/(\w+)|\[([^\]]+)\]/g,n=[];t=r.exec(e||"");)"["===e[t.index]?n.push(t[2]):n.push(t[1]);return n}function a(e,t){for(var r=c(t)?t:s(t),n=r.length,o=0;ot.length)&&(e=t.length);for(var r=0,n=new Array(e);rt.length)&&(e=t.length);for(var r=0,n=new Array(e);r -
- Example 1 -
- diff --git a/docs/.examples/Intro/Intro1.vue b/docs/.examples/Intro/Intro1.vue new file mode 100755 index 0000000..72b1043 --- /dev/null +++ b/docs/.examples/Intro/Intro1.vue @@ -0,0 +1,16 @@ + diff --git a/docs/.examples/Intro/Intro2.vue b/docs/.examples/Intro/Intro2.vue new file mode 100755 index 0000000..2670ff3 --- /dev/null +++ b/docs/.examples/Intro/Intro2.vue @@ -0,0 +1,16 @@ + diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index d127ab2..377f90e 100755 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -13,7 +13,18 @@ module.exports = { nav: [{ text: 'Github', link: 'https://github.com/kouts/vue-path-store' }], sidebar: [ ['/', 'Introduction'], - ['/installation/', 'Installation'] + { + title: 'PathStore', + collapsable: false, + children: [ + ['/path-store/installation/', 'Installation'], + ['/path-store/usage/', 'Usage'], + ['/path-store/api/', 'API'] + ] + }, + ['/path-store-vuex-plugin/', 'PathStore Vuex Plugin'], + ['/path-store-pinia-plugin/', 'PathStore Pinia Plugin'], + // ['/examples', 'Examples'] ] }, head: [ diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js index 29ce23c..16a61b8 100755 --- a/docs/.vuepress/enhanceApp.js +++ b/docs/.vuepress/enhanceApp.js @@ -1,4 +1,5 @@ import './styles/styles.scss' +import { createPathStore } from '../../src/pathStore.js' export default ({ Vue, // the version of Vue being used in the VuePress app @@ -8,5 +9,8 @@ export default ({ }) => { if (typeof process === 'undefined') { // process is undefined in a browser + Vue.prototype.$s = createPathStore({ + state: {} + }) } } diff --git a/docs/.vuepress/public/vue-path-store.png b/docs/.vuepress/public/vue-path-store.png new file mode 100755 index 0000000..5cba8bb Binary files /dev/null and b/docs/.vuepress/public/vue-path-store.png differ diff --git a/docs/.vuepress/styles/styles.scss b/docs/.vuepress/styles/styles.scss index 429c1fa..0617778 100755 --- a/docs/.vuepress/styles/styles.scss +++ b/docs/.vuepress/styles/styles.scss @@ -64,6 +64,10 @@ blockquote p {color: #777;} /* Font sizes */ .font-16 {font-size: 16px;} +h3, .h3 { + font-size: 1.25rem !important; +} + /* Browsers support table */ #browsers-support + table .icon.outbound { display: none; @@ -80,11 +84,11 @@ blockquote p {color: #777;} box-sizing: content-box; } -/* Custom width for dataset pages */ -.dataset-page .theme-default-content:not(.custom) { - max-width: 100%; +/* H2 margin fix */ +.theme-default-content:not(.custom) > h2, .theme-default-content:not(.custom) > h3 { + margin-bottom: 1rem !important; } -.dataset-page .theme-default-content:not(.custom) ul.pagination a:hover { - text-decoration: none; +.theme-default-content:not(.custom) > h2:first-child + p { + margin-top: 0 !important; } \ No newline at end of file diff --git a/docs/examples/index.md b/docs/examples/index.md new file mode 100644 index 0000000..acfebc0 --- /dev/null +++ b/docs/examples/index.md @@ -0,0 +1 @@ +## Examples \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index 12f83e6..5f4e051 100755 --- a/docs/index.md +++ b/docs/index.md @@ -1,8 +1,47 @@ -# vue-path-store +
+ Vue PathStore +
-`vue-path-store` is a lightweight shared state management solution for Vue. -At it's heart lays a simple reactive store, that uses the dot notation path syntax for data mutation. +**PathStore** is a lightweight **shared** state management solution for Vue. +At its heart lays a simple reactive store, which uses the dot notation path syntax for data mutation. -It also comes with a Vuex plugin flavor so that you can use the dot notation to mutate state in Vuex. +It also comes with: - \ No newline at end of file +- a **[Vuex](https://vuex.vuejs.org/) plugin** so that you can use dot notation along with the +full power of Vuex (actions, getters, modules, devtools) as well. + +- a **[Pinia](https://pinia.esm.dev/) plugin** so that you can use dot notation with your favorite fruity store. + +## Features +- Share state easily between components using either the object or composition API +- Use dot (or bracket) notation for mutating state (set, delete) +- Creates intermediate reactive object/array structures if not available while setting state +- Avoid Vue [change detection caveats](https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats) +- Extra methods for `Array` manipulation +- Enhance Vuex with dot notation by utilizing the PathStore Vuex Plugin +- Enhance Pinia with dot notation by utilizing the PathStore Pinia Plugin + +## Browsers support + +| [IE / Edge](http://godban.github.io/browsers-support-badges/)
IE / Edge | [Firefox](http://godban.github.io/browsers-support-badges/)
Firefox | [Chrome](http://godban.github.io/browsers-support-badges/)
Chrome | [Safari](http://godban.github.io/browsers-support-badges/)
Safari | [Opera](http://godban.github.io/browsers-support-badges/)
Opera | +| --------- | --------- | --------- | --------- | --------- | +| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions + +## Quick shared state example + +```js +import Vue from 'vue' +import { createPathStore } from 'vue-path-store' + +// Initialize the store and provide it to all components +Vue.prototype.$s = createPathStore({ + state: {} +}) + +``` + +**Component 1** + + +**Component 2** + \ No newline at end of file diff --git a/docs/installation/index.md b/docs/installation/index.md deleted file mode 100755 index 3287b59..0000000 --- a/docs/installation/index.md +++ /dev/null @@ -1,3 +0,0 @@ -## Basic - -## Module \ No newline at end of file diff --git a/docs/path-store-pinia-plugin/index.md b/docs/path-store-pinia-plugin/index.md new file mode 100755 index 0000000..d557f2c --- /dev/null +++ b/docs/path-store-pinia-plugin/index.md @@ -0,0 +1,95 @@ +##### PathStore Pinia Plugin brings PathStore's [API](../path-store/api/) to [Pinia](https://pinia.esm.dev/). + +All methods from PathStore's API are registered as Pinia actions. + +This way you get a simplified overall Pinia development experience, while still having +basic devtools monitoring. + +## Installation + +### Basic + +Download the repo, extract ```pathStorePiniaPlugin.min.js``` out of the ```dist/umd``` folder +and insert it in your page. + +``` html + +``` + +### Module System + +Install it via npm +```sh +npm i vue-path-store +``` + +Use the ```import``` statement to include it into your js +``` js +import { pathStorePiniaPlugin } from 'vue-path-store/dist/es/pathStorePiniaPlugin.js' +``` + +## Usage + +PathStore Pinia Plugin is registered like any other Pinia plugin. + +```js +// main.js +import Vue from 'vue' +import VueCompositionApi from '@vue/composition-api' +import { PiniaPlugin, createPinia } from 'pinia' +import { pathStorePiniaPlugin } from './modules/path_store/pathStorePiniaPlugin' + +Vue.use(VueCompositionApi) +Vue.use(PiniaPlugin) + +const pinia = createPinia() +pinia.use(pathStorePiniaPlugin) + +new Vue({ + el: '#app', + pinia +}) +``` + +```js +// store.js +import { defineStore } from 'pinia' + +export const usePiniaStore = defineStore({ + id: 'pinia', + state() { + return { + message: '' + } + } +}) +``` + +Using it inside components +```vue + + + + +``` \ No newline at end of file diff --git a/docs/path-store-vuex-plugin/index.md b/docs/path-store-vuex-plugin/index.md new file mode 100755 index 0000000..9206ef4 --- /dev/null +++ b/docs/path-store-vuex-plugin/index.md @@ -0,0 +1,97 @@ +##### PathStore Vuex Plugin brings PathStore's [API](../path-store/api/) to [Vuex](https://vuex.vuejs.org/). + +All methods from PathStore's API are registered as Vuex methods that trigger +equivalent generic **mutations**. This way you get a boilerplate-free Vuex development experience, while still having +devtools monitoring. + +Since Vuex modules use the root Vuex state, you can use the PathStore Vuex Plugin methods to set/get the state +of Vuex modules as well. + +You can use **all** Vuex features as before (getters/actions/mutations etc) or mix and match. + +## Vuex Methods + +Here's a quick list of the methods that are added to Vuex with the PathStore Vuex Plugin. +For more details you can refer to the [PathStore API section](../path-store/api/) + +| Method | Short description | Mutation | +|--------------------------------------------|------------------------------------------------|----------| +| `set(path, value)` or `set(map)` | Sets a value | set | +| `get(path)` | Gets a value | get | +| `toggle(path)` | Toggles a value | toggle | +| `del(path)` or `del(array)` | Works like the delete operator | del | +| `pop(path)` | Removes and returns last element of array | pop | +| `push(path, value[, ...valueN])` | Appends elements to the end of array | push | +| `reverse(path)` | Reverses an array | reverse | +| `shift(path)` | Removes and returns first element of array | shift | +| `sort(path[, compareFunction])` | Sorts an array | sort | +| `splice(path, index, [removeCount[, add]])`| Removes or replaces array elements | splice | +| `unshift(path, value[, ...valueN])` | Inserts elements to the beginning of array | unshift | + +## Installation + +### Basic + +Download the repo, extract ```pathStoreVuexPlugin.min.js``` out of the ```dist/umd``` folder +and insert it in your page. + +``` html + +``` + +### Module System + +Install it via npm +```sh +npm i vue-path-store +``` + +Use the ```import``` statement to include it into your js +``` js +import { pathStoreVuexPlugin } from 'vue-path-store/dist/es/pathStoreVuexPlugin.js' +``` + +## Usage + +PathStore Vuex Plugin is registered like any other Vuex plugin. + +```js +import Vue from 'vue' +import Vuex from 'vuex' +import { pathStoreVuexPlugin } from 'vue-path-store/dist/es/pathStoreVuexPlugin.js' + +Vue.use(Vuex) + +const store = new Vuex.Store({ + plugins: [pathStoreVuexPlugin], + state: { + count: 0, + message: '' + }, + // You can register mutations as usual + mutations: { + increment (state) { + state.count++ + } + } +}) + +new Vue({ + el: '#app', + store +}) +``` + +Using it inside components +```vue + +``` \ No newline at end of file diff --git a/docs/path-store/api/index.md b/docs/path-store/api/index.md new file mode 100644 index 0000000..d466020 --- /dev/null +++ b/docs/path-store/api/index.md @@ -0,0 +1,351 @@ +## set + +Sets one or many a reactive properties by using either `path, value` or a map of `path: value` pairs. +It will create intermediate objects and arrays depending on the type of the key (string or number) in the path. +`set` uses [Vue.set](https://vuejs.org/v2/api/#Vue-set) internally, ensuring that new properties are also reactive +and that view updates are triggered. + +### Syntax + +- `set(path, value)` +- `set(map)` + +### Parameters + +- `path (string)`: The path of the data we're changing, e.g. + - user + - user.name + - user.friends[1] or user.friends.1 +- `value (any)`: The value we're changing it to. It can be a primitive or an object (or array). +- `map (Object)`: A map of `path: value` pairs. + +### Example +```js +$s.set('state.bar.baz', 'New value') +// This will set state.bar.baz to 'New value' +// If intermediate objects don't exist +// they will get automatically created + +$s.set({ + 'state.bar.baz', 'New value', + 'state.qux': 'Another value' +}) +// Use a map of path/values to set multiple properties +``` + +## get + +Returns the value at `path`. + +### Syntax + +- `get(path)` + +### Parameters + +- `path (string)`: The path of the data to retrieve. If omitted, it returns the store's data. + +### Returns + +- `(any)`: Returns the data that exists at the given path, or the store's data if no path is given. + It returns `undefined` if no value is found at the given path, the path does not exist, or its invalid. + +### Example +```js +/* +Assuming the data inside store is +{ + state: { + bar: 'baz' + }, + arr: ['test1', 'test2'] +} +*/ + +$s.get('state.bar') +// Will return 'baz' + +$s.get('arr[1]') +// or +$s.get('arr.1') +// Will return 'test2' + +$s.get('foo.qux') +// Will return undefined +``` + +## toggle + +Toggles the given `path`. +In other words, if `foo` is truthy, then `toggle('foo')` will make it false, and vice-versa. + +### Syntax + +- `toggle(path)` + +### Parameters + +- `path (string)`: The path to toggle the value of. + +### Example +```js +/* +Assuming the data inside store is +{ + state: { + bar: false + } +} +*/ +$s.toggle('state.bar') +// This will set state.bar to true +``` + +## del + +Deletes one or many object properties or array elements by using either `path, value` or an array of `path`s. +`del` uses [Vue.delete](https://vuejs.org/v2/api/#Vue-delete) internally, to ensure that the deletion triggers view updates. + +### Syntax + +- `del(path)` +- `del(array)` + +### Parameters + +- `path (string)`: The path of the data we're deleteing, e.g. + - user.name + - user.friends[1] or user.friends.1 +- `map (Array)`: An array of `path`s to delete. + +### Example +```js +$s.del('state.bar.baz') +// This will delete the state.bar.baz property + +$s.del('state.bar.baz', 'state.qux']) +// Delete multiple properties by using an array of paths +``` + +## pop + +Equivalent to `Array.pop`, removes an element from the end of the array at the given path. +Only works on arrays. + +### Syntax + +- `pop(path)` + +### Parameters + +- `path (string)`: The path of the array to remove the element from, e.g. `list` or `order.items`. + +### Returns + +- `(any)`: Returns the removed array element. + +### Example +```js +/* +Assuming the data inside store is +{ + arr: ['test1', 'test2'] +} +*/ + +const removed = $s.pop('arr') +// This will remove the element element of the array. +// removed will now be 'test2' +``` + +## push + +Equivalent to `Array.push`, appends one or more elements to the end of the array at the given path. +Only works on arrays. + +### Syntax + +- `push(path, value[, ...valueN])` + +### Parameters + +- `path (string)`: The path of the array to insert the element to, e.g. `list` or `order.items`. +- `value (any)`: The value to append to the end of the array. One or more values may be supplied. + +### Returns + +- `(number)`: The new length property of the array upon which the method was called. + +### Example +```js +/* +Assuming the data inside store is +{ + arr: ['test1', 'test2'] +} +*/ + +$s.push('arr', 'test3', 'test4') +// This will append 'test3' and 'test4' to the arr array. +// arr will now be ['test1', 'test2', 'test3', 'test4'] +``` + +## reverse + +Equivalent to `Array.reverse`, reverses the array at the given path. +Only works on arrays. + +### Syntax + +- `reverse(path)` + +### Parameters + +- `path (string)`: The path of the array to reverse, e.g. `list` or `order.items`. + +### Returns + +- `(Array)`: The reversed array. + +### Example +```js +/* +Assuming the data inside store is +{ + arr: ['test1', 'test2', 'test3'] +} +*/ + +$s.reverse('arr') +// arr will now be ['test3', 'test2', 'test1'] +``` + +## shift + +Equivalent to `Array.shift`, removes an element from the beginning of the array at the given path. +Only works on arrays. + +### Syntax + +- `shift(path)` + +### Parameters + +- `path (string)`: The path of the array to remove the element from, e.g. `list` or `order.items`. + +### Returns + +- `(any)`: Returns the removed array element. + +### Example +```js +/* +Assuming the data inside store is +{ + arr: ['test1', 'test2'] +} +*/ + +const removed = $s.shift('arr') +// This will remove the first element from the array. +// removed will now be 'test1' +``` + +## sort + +Equivalent to `Array.sort`, sorts the array at the given path. +Only works on arrays. + +### Syntax + +- `sort(path[, compareFunction])` + +### Parameters + +- `path (string)`: The path of the array to sort, e.g. `list` or `order.items`. +- `compareFunction (Function)`: A function that defines the sort order. + +### Returns + +- `(Array)`: The sorted array. + +### Example +```js +/* +Assuming the data inside store is +{ + arr: ['c', 'b', 'a', 'd', 'f' ] +} +*/ + +$s.sort('arr') +// arr will now be ['a', 'b', 'c', 'd', 'f'] +``` + +## splice + +Equivalent to `Array.splice`, removes or replaces existing elements and/or adds new elements in place at the given path. +Only works on arrays. + +### Syntax + +- `splice(path, index, [removeCount[, add]])` + +### Parameters + +- `path (string)`: The path of the array to splice, e.g. `list` or `order.items`. +- `index (number)`: The index at which to start the operation. +- `[removeCount] (number)`: The number of elements to remove starting with the element at `index`. +This may be 0 if you don't want to remove any elements. +- `[add] (any)`: Any elements to insert into the array starting at `index`. +There can be 0 or more elements passed to add to the array. + +### Returns + +- `(Array)`: An array containing the deleted elements. +If no elements are removed, an empty array is returned. + +### Example +```js +/* +Assuming the data inside store is +{ + arr: ['test1', 'test2', 'test3'] +} +*/ + +const removed = $s.splice('arr', 0, 1) +// arr will now be ['test2', 'test3'] +// removed will be ['test1'] +``` + +## unshift + +Equivalent to `Array.unshift`, adds one or more elements to the beginning of an array at the given path. +Only works on arrays. + +### Syntax + +- `unshift(path, value[, ...valueN])` + +### Parameters + +- `path (string)`: The path of the array to add the element(s) to, e.g. `list` or `order.items`. +- `value (any)`: The value to prepend to the beginning of the array. One or more values may be supplied. + +### Returns + +- `(number)`: The new length property of the array upon which the method was called. + +### Example +```js +/* +Assuming the data inside store is +{ + arr: ['test3', 'test4'] +} +*/ + +$s.unshift('arr', 'test1', 'test2') +// arr will now be ['test1', 'test2', 'test3', 'test4'] +``` diff --git a/docs/path-store/installation/index.md b/docs/path-store/installation/index.md new file mode 100755 index 0000000..d9f5cd9 --- /dev/null +++ b/docs/path-store/installation/index.md @@ -0,0 +1,20 @@ +## Basic + +Download the repo, extract ```pathStore.min.js``` out of the ```dist/umd``` folder +and insert it in your page. + +``` html + +``` + +## Module System + +Install it via npm +```sh +npm i vue-path-store +``` + +Use the ```import``` statement to include it into your js +``` js +import { createPathStore } from 'vue-path-store' +``` \ No newline at end of file diff --git a/docs/path-store/usage/index.md b/docs/path-store/usage/index.md new file mode 100644 index 0000000..3d3097f --- /dev/null +++ b/docs/path-store/usage/index.md @@ -0,0 +1,124 @@ +## Object API + +Since PathStore is just an object, you can share it as any other object in Vue. +You can use `Vue.prototype`, a `mixin`, `provide/inject`, `import/export` etc. + +### Sharing the store via the `Vue.prototype` + +In the app's entry point (e.g `main.js`) +```js +import Vue from 'vue' +import { createPathStore } from 'vue-path-store' + +// Initialize the store and provide it to all components +Vue.prototype.$s = createPathStore({ + state: { + message: 'Hello world' + } +}) +``` + +Using it inside components +```vue + +``` + +### Sharing the store with `import/export` + +Create the store in a separate file (e.g `store.js`) +```js +import { createPathStore } from 'vue-path-store' + +// Initialize the store and export it +export const store = createPathStore({ + state: { + message: 'Hello world' + } +}) +``` + +Import it inside components +```vue + + + + +``` + +## Composition API + +You can use PathStore with the Vue [composition-api](https://github.com/vuejs/composition-api) with just `import/export` +or using a composable function. + +### Sharing the store with a composable function + +Create the store in a separate file (e.g `useStore.js`) +```js +import { createPathStore } from 'vue-path-store' + +const store = createPathStore({ + state: { + message: 'Hello world!' + } +}) + +const useStore = () => store + +export { useStore } + +``` + +Import it inside components +```vue + + + + +``` \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a2d1a21..1aef5f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2868,17 +2868,6 @@ "color-convert": "^2.0.1" } }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, "cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -2911,31 +2900,12 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "optional": true - }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true }, - "loader-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", - "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", - "dev": true, - "optional": true, - "requires": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^2.1.2" - } - }, "ssri": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", @@ -2956,28 +2926,6 @@ "strip-ansi": "^6.0.0" } }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "vue-loader-v16": { - "version": "npm:vue-loader@16.2.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz", - "integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==", - "dev": true, - "optional": true, - "requires": { - "chalk": "^4.1.0", - "hash-sum": "^2.0.0", - "loader-utils": "^2.0.0" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -3065,6 +3013,23 @@ } } }, + "@vue/composition-api": { + "version": "1.0.0-rc.9", + "resolved": "https://registry.npmjs.org/@vue/composition-api/-/composition-api-1.0.0-rc.9.tgz", + "integrity": "sha512-U//BqmGRVaPyZbYsPfRlmCKnnFkhRzUBu7cjrWn4PSwQ5Oh+M0KcYIHlupUd+Qmd8KwaiYiuUpJLncl3wFsrdg==", + "dev": true, + "requires": { + "tslib": "^2.2.0" + }, + "dependencies": { + "tslib": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz", + "integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==", + "dev": true + } + } + }, "@vue/preload-webpack-plugin": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@vue/preload-webpack-plugin/-/preload-webpack-plugin-1.1.2.tgz", @@ -14209,6 +14174,12 @@ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true }, + "pinia": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-0.5.1.tgz", + "integrity": "sha512-EPapne/rmqHlYUEIou9xn0MdC8IVbr+lGRUcpuWVQJ5inpR9MjcXNJC2XILibErxhV0IohSGSfxg3XHV6loKYg==", + "dev": true + }, "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", @@ -18191,6 +18162,87 @@ } } }, + "vue-loader-v16": { + "version": "npm:vue-loader@16.2.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.2.0.tgz", + "integrity": "sha512-TitGhqSQ61RJljMmhIGvfWzJ2zk9m1Qug049Ugml6QP3t0e95o0XJjk29roNEiPKJQBEi8Ord5hFuSuELzSp8Q==", + "dev": true, + "optional": true, + "requires": { + "chalk": "^4.1.0", + "hash-sum": "^2.0.0", + "loader-utils": "^2.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "dev": true, + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "optional": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "optional": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "optional": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "optional": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "vue-router": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-3.5.1.tgz", diff --git a/package.json b/package.json index 7c6572e..943ed54 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "@vue/cli-plugin-babel": "^4.5.13", "@vue/cli-plugin-unit-jest": "^4.5.13", "@vue/cli-service": "^4.5.13", + "@vue/composition-api": "^1.0.0-rc.9", "@vue/test-utils": "^1.1.4", "bootstrap": "^4.6.0", "core-js": "^3.12.1", @@ -60,6 +61,7 @@ "husky": "^6.0.0", "lint-staged": "^11.0.0", "node-sass": "^4.14.1", + "pinia": "^0.5.1", "prettier": "2.2.1", "rollup": "^2.45.2", "rollup-plugin-terser": "^5.3.1", diff --git a/prettier.config.js b/prettier.config.js index 93470de..cc1f5e6 100644 --- a/prettier.config.js +++ b/prettier.config.js @@ -1 +1,6 @@ -module.exports = require('eslint-config-kouts/prettier.config.js') +const config = require('eslint-config-kouts/prettier.config.js') +const projectConfig = { + printWidth: 80 +} + +module.exports = Object.assign(config, projectConfig) diff --git a/rollup.config.js b/rollup.config.js index d6275ee..904f63c 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -6,7 +6,7 @@ const globals = { vue: 'Vue' } -const rollupConfigs = ['pathStore', 'pathStoreVuexPlugin'].map((name) => ({ +const rollupConfigs = ['pathStore', 'pathStoreVuexPlugin', 'pathStorePiniaPlugin'].map((name) => ({ input: `src/${name}.js`, external: ['vue', 'vuex'], output: [ diff --git a/src/methods.js b/src/methods.js new file mode 100644 index 0000000..ccbb4d5 --- /dev/null +++ b/src/methods.js @@ -0,0 +1,31 @@ +import { getByPath, isArray } from 'vue-set-path/dist/es/utils' +import { setOne, setMany, deleteMany } from 'vue-set-path' +import { ARRAY_METHODS } from './constants.js' + +export function createPathStoreMethods() { + return { + set(path, value) { + setMany(this, path, value) + }, + toggle(path) { + setOne(this, path, !getByPath(this, path)) + }, + get(path) { + return path ? getByPath(this, path) : this + }, + del(path) { + deleteMany(this, path) + }, + ...ARRAY_METHODS.reduce(function (acc, method) { + const fn = function (...args) { + const path = args.shift() + const arr = getByPath(this, path) + if (!isArray(arr)) { + throw Error('Argument must be an array.') + } + return arr[method](...args) + } + return Object.assign(acc, { [method]: fn }) + }, {}) + } +} diff --git a/src/pathStore.js b/src/pathStore.js index 35105ad..6d7c151 100644 --- a/src/pathStore.js +++ b/src/pathStore.js @@ -1,38 +1,4 @@ import Vue from 'vue' -import { getByPath, isArray } from 'vue-set-path/dist/es/utils' -import { setOne, setMany, deleteMany } from 'vue-set-path' -import { ARRAY_METHODS } from './constants.js' +import { createPathStoreMethods } from './methods' -const createPathStore = (state) => { - const store = Vue.observable(state) - - const methods = { - set(path, value) { - setMany(store, path, value) - }, - toggle(path) { - setOne(store, path, !getByPath(store, path)) - }, - get(path) { - return path ? getByPath(store, path) : store - }, - del(path) { - deleteMany(store, path) - }, - ...ARRAY_METHODS.reduce((acc, method) => { - const fn = (...args) => { - const path = args.shift() - const arr = getByPath(store, path) - if (!isArray(arr)) { - throw Error('Argument must be an array.') - } - return arr[method](...args) - } - return Object.assign(acc, { [method]: fn }) - }, {}) - } - - return Object.assign(store, methods) -} - -export { createPathStore } +export const createPathStore = (state) => Object.assign(Vue.observable(state), createPathStoreMethods()) \ No newline at end of file diff --git a/src/pathStorePiniaPlugin.js b/src/pathStorePiniaPlugin.js new file mode 100644 index 0000000..05bcc92 --- /dev/null +++ b/src/pathStorePiniaPlugin.js @@ -0,0 +1,4 @@ +import { createPathStoreMethods } from './methods' + +export const pathStorePiniaPlugin = (ctx) => + Object.assign((ctx.store.actions = ctx.store.actions || {}), createPathStoreMethods()) \ No newline at end of file diff --git a/tests/unit/pathStorePiniaPlugin.spec.js b/tests/unit/pathStorePiniaPlugin.spec.js new file mode 100644 index 0000000..2b989c2 --- /dev/null +++ b/tests/unit/pathStorePiniaPlugin.spec.js @@ -0,0 +1,132 @@ +import { waitNT, dataOf } from '../utils' +import VueCompositionApi from '@vue/composition-api' +import { createLocalVue, mount, enableAutoDestroy } from '@vue/test-utils' +import { PiniaPlugin, defineStore, createPinia, mapStores } from 'pinia' +import { pathStorePiniaPlugin } from '@/pathStorePiniaPlugin' + +enableAutoDestroy(afterEach) + +const localVue = createLocalVue() +localVue.use(VueCompositionApi) +localVue.use(PiniaPlugin) + +const pinia = createPinia() +pinia.use(pathStorePiniaPlugin) + +const createTestComponent = (useTestStore) => ({ + template: '
{{ testStore.data }}
', + computed: { + ...mapStores(useTestStore) + } +}) + +describe('pathStorePiniaPlugin', () => { + let useTestStore + let wrapper + + beforeEach(() => { + useTestStore = defineStore({ + id: 'test', + state() { + return { + data: null + } + } + }) + const TestComponent = createTestComponent(useTestStore) + wrapper = mount(TestComponent, { pinia, localVue }) + }) + + it('sets a string', async () => { + wrapper.vm.testStore.set('data', 'Test') + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toBe('Test') + }) + + it('sets an object', async () => { + const obj = { + foo: { + bar: { + str: 'test', + num: 10 + } + } + } + wrapper.vm.testStore.set('data', obj) + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual(obj) + }) + + it('updates an object', async () => { + const obj = { + foo: { + bar: { + str: 'test', + num: 10 + } + } + } + wrapper.vm.testStore.set('data', obj) + wrapper.vm.testStore.set('data.bar.str', 'Updated') + await waitNT(wrapper.vm) + const data = dataOf(wrapper) + expect(data.bar.str).toBe('Updated') + }) + + it('pops an array element', async () => { + const arr = [1, 2, 3, 4] + wrapper.vm.testStore.set('data', arr) + wrapper.vm.testStore.pop('data') + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual([1, 2, 3]) + }) + + it('pushes an element into an array', async () => { + const arr = [1, 2, 3] + wrapper.vm.testStore.set('data', arr) + wrapper.vm.testStore.push('data', 4) + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual([1, 2, 3, 4]) + }) + + it('reverses an an array', async () => { + const arr = [1, 2, 3] + wrapper.vm.testStore.set('data', arr) + wrapper.vm.testStore.reverse('data') + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual([3, 2, 1]) + }) + + it('removes the first element of an array', async () => { + const arr = [1, 2, 3, 4] + wrapper.vm.testStore.set('data', arr) + wrapper.vm.testStore.shift('data') + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual([2, 3, 4]) + }) + + it('sorts an array', async () => { + const arr = [2, 4, 1, 3] + wrapper.vm.testStore.set('data', arr) + wrapper.vm.testStore.sort('data') + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual([1, 2, 3, 4]) + }) + + it('splices an array', async () => { + const arr = [1, 2, 3, 4] + wrapper.vm.testStore.set('data', arr) + wrapper.vm.testStore.splice('data', 0, 2) + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual([3, 4]) + }) + + it('adds elements to the beginning of an array', async () => { + const arr = [3, 4] + wrapper.vm.testStore.set('data', arr) + wrapper.vm.testStore.unshift('data', 1, 2) + await waitNT(wrapper.vm) + expect(dataOf(wrapper)).toEqual([1, 2, 3, 4]) + }) + +})