From 2885a0ae86293c98ddf93aa247561eefe49bcf20 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 20 May 2022 13:09:04 +0800 Subject: [PATCH 1/6] Update store index.js -> index.ts --- packages/js/notices/src/store/{index.js => index.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename packages/js/notices/src/store/{index.js => index.ts} (100%) diff --git a/packages/js/notices/src/store/index.js b/packages/js/notices/src/store/index.ts similarity index 100% rename from packages/js/notices/src/store/index.js rename to packages/js/notices/src/store/index.ts From 286358d7248462faf30e264040a26febb4268d8d Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 20 May 2022 13:09:25 +0800 Subject: [PATCH 2/6] Migrate @woocommerce/notice to TS --- packages/js/notices/package.json | 4 + .../js/notices/src/{index.js => index.ts} | 0 .../src/store/{actions.js => actions.ts} | 80 ++++++++++++------- .../src/store/{constants.js => constants.ts} | 2 +- .../src/store/{controls.js => controls.ts} | 7 +- packages/js/notices/src/store/index.ts | 19 ++++- .../src/store/{reducer.js => reducer.ts} | 20 +++-- .../src/store/{selectors.js => selectors.ts} | 5 +- packages/js/notices/src/store/types.ts | 10 +++ .../utils/{on-sub-key.js => on-sub-key.ts} | 18 ++++- packages/js/notices/tsconfig.json | 7 +- 11 files changed, 122 insertions(+), 50 deletions(-) rename packages/js/notices/src/{index.js => index.ts} (100%) rename packages/js/notices/src/store/{actions.js => actions.ts} (76%) rename packages/js/notices/src/store/{constants.js => constants.ts} (88%) rename packages/js/notices/src/store/{controls.js => controls.ts} (63%) rename packages/js/notices/src/store/{reducer.js => reducer.ts} (54%) rename packages/js/notices/src/store/{selectors.js => selectors.ts} (94%) create mode 100644 packages/js/notices/src/store/types.ts rename packages/js/notices/src/store/utils/{on-sub-key.js => on-sub-key.ts} (70%) diff --git a/packages/js/notices/package.json b/packages/js/notices/package.json index 927b41d98ee..fb1fc95e9cd 100644 --- a/packages/js/notices/package.json +++ b/packages/js/notices/package.json @@ -20,6 +20,7 @@ }, "main": "build/index.js", "module": "build-module/index.js", + "types": "build-types", "react-native": "src/index", "dependencies": { "@wordpress/a11y": "^3.5.0", @@ -45,11 +46,14 @@ "lint:fix": "eslint src --fix" }, "devDependencies": { + "@automattic/data-stores": "^2.0.1", "@babel/core": "^7.17.5", "@woocommerce/eslint-plugin": "workspace:*", + "@woocommerce/data": "workspace:*", "eslint": "^8.12.0", "jest": "^27.5.1", "jest-cli": "^27.5.1", + "redux": "^4.2.0", "rimraf": "^3.0.2", "ts-jest": "^27.1.3", "typescript": "^4.6.2" diff --git a/packages/js/notices/src/index.js b/packages/js/notices/src/index.ts similarity index 100% rename from packages/js/notices/src/index.js rename to packages/js/notices/src/index.ts diff --git a/packages/js/notices/src/store/actions.js b/packages/js/notices/src/store/actions.ts similarity index 76% rename from packages/js/notices/src/store/actions.js rename to packages/js/notices/src/store/actions.ts index 59fa585e0ef..2f2b46bf9eb 100644 --- a/packages/js/notices/src/store/actions.js +++ b/packages/js/notices/src/store/actions.ts @@ -2,22 +2,25 @@ * External dependencies */ import { uniqueId } from 'lodash'; +import { Status, Action as WPNoticeAction } from '@wordpress/notices'; /** * Internal dependencies */ import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants'; -/** - * @typedef {Object} WPNoticeAction Object describing a user action option associated with a notice. - * - * @property {string} label Message to use as action label. - * @property {?string} url Optional URL of resource if action incurs - * browser navigation. - * @property {?Function} onClick Optional function to invoke when action is - * triggered by user. - * - */ +export type Options = { + id: string; + context: string; + isDismissible: boolean; + type: string; + speak: boolean; + actions: Array< WPNoticeAction >; + icon: null | JSX.Element; + explicitDismiss: boolean; + onDismiss: ( () => void ) | null; + __unstableHTML?: boolean; +}; /** * Returns an action object used in signalling that a notice is to be created. @@ -46,30 +49,43 @@ import { DEFAULT_CONTEXT, DEFAULT_STATUS } from './constants'; * can't be dismissed by clicking * the body of the notice. * @param {Function} [options.onDismiss] Called when the notice is dismissed. + * @param {boolean} [options.__unstableHTML] Notice message as raw HTML. * - * @return {Object} Action object. + * @return {Object} WPNoticeAction object. */ -export function createNotice( status = DEFAULT_STATUS, content, options = {} ) { - const { - speak = true, - isDismissible = true, - context = DEFAULT_CONTEXT, - id = uniqueId( context ), - actions = [], - type = 'default', +export function createNotice( + status: Status = DEFAULT_STATUS, + content: string, + { + speak, + isDismissible, + context, + id, + actions, + type, __unstableHTML, - icon = null, - explicitDismiss = false, - onDismiss = null, - } = options; - + icon, + explicitDismiss, + onDismiss, + }: Options = { + speak: true, + isDismissible: true, + context: DEFAULT_CONTEXT, + id: uniqueId( DEFAULT_CONTEXT ), + actions: [], + type: 'default', + icon: null, + explicitDismiss: false, + onDismiss: null, + } +) { // The supported value shape of content is currently limited to plain text // strings. To avoid setting expectation that e.g. a WPElement could be // supported, cast to a string. content = String( content ); return { - type: 'CREATE_NOTICE', + type: 'CREATE_NOTICE' as const, context, notice: { id, @@ -98,7 +114,7 @@ export function createNotice( status = DEFAULT_STATUS, content, options = {} ) { * * @return {Object} Action object. */ -export function createSuccessNotice( content, options ) { +export function createSuccessNotice( content: string, options: Options ) { return createNotice( 'success', content, options ); } @@ -113,7 +129,7 @@ export function createSuccessNotice( content, options ) { * * @return {Object} Action object. */ -export function createInfoNotice( content, options ) { +export function createInfoNotice( content: string, options: Options ) { return createNotice( 'info', content, options ); } @@ -128,7 +144,7 @@ export function createInfoNotice( content, options ) { * * @return {Object} Action object. */ -export function createErrorNotice( content, options ) { +export function createErrorNotice( content: string, options: Options ) { return createNotice( 'error', content, options ); } @@ -143,7 +159,7 @@ export function createErrorNotice( content, options ) { * * @return {Object} Action object. */ -export function createWarningNotice( content, options ) { +export function createWarningNotice( content: string, options: Options ) { return createNotice( 'warning', content, options ); } @@ -156,10 +172,12 @@ export function createWarningNotice( content, options ) { * * @return {Object} Action object. */ -export function removeNotice( id, context = DEFAULT_CONTEXT ) { +export function removeNotice( id: string, context: string = DEFAULT_CONTEXT ) { return { - type: 'REMOVE_NOTICE', + type: 'REMOVE_NOTICE' as const, id, context, }; } + +export type Action = ReturnType< typeof createNotice | typeof removeNotice >; diff --git a/packages/js/notices/src/store/constants.js b/packages/js/notices/src/store/constants.ts similarity index 88% rename from packages/js/notices/src/store/constants.js rename to packages/js/notices/src/store/constants.ts index 2949bde0577..f0a3fbcb622 100644 --- a/packages/js/notices/src/store/constants.js +++ b/packages/js/notices/src/store/constants.ts @@ -12,4 +12,4 @@ export const DEFAULT_CONTEXT = 'global'; * * @type {string} */ -export const DEFAULT_STATUS = 'info'; +export const DEFAULT_STATUS = 'info' as const; diff --git a/packages/js/notices/src/store/controls.js b/packages/js/notices/src/store/controls.ts similarity index 63% rename from packages/js/notices/src/store/controls.js rename to packages/js/notices/src/store/controls.ts index 704352289d5..6da646a78e7 100644 --- a/packages/js/notices/src/store/controls.js +++ b/packages/js/notices/src/store/controls.ts @@ -3,8 +3,13 @@ */ import { speak } from '@wordpress/a11y'; +export type Action = { + message: string; + ariaLive?: string; +}; + export default { - SPEAK( action ) { + SPEAK( action: Action ) { speak( action.message, action.ariaLive || 'assertive' ); }, }; diff --git a/packages/js/notices/src/store/index.ts b/packages/js/notices/src/store/index.ts index d4ebaff9a6a..64cf25b27de 100644 --- a/packages/js/notices/src/store/index.ts +++ b/packages/js/notices/src/store/index.ts @@ -2,6 +2,8 @@ * External dependencies */ import { registerStore } from '@wordpress/data'; +import { SelectFromMap, DispatchFromMap } from '@automattic/data-stores'; +import { Reducer, AnyAction } from 'redux'; /** * Internal dependencies @@ -9,11 +11,24 @@ import { registerStore } from '@wordpress/data'; import reducer from './reducer'; import * as actions from './actions'; import * as selectors from './selectors'; +import { State } from './types'; +export * from './types'; +export const STORE_NAME = 'core/notices2'; // NOTE: This uses core/notices2, if this file is copied back upstream // to Gutenberg this needs to be changed back to core/notices. -export default registerStore( 'core/notices2', { - reducer, +export default registerStore< State >( STORE_NAME, { + reducer: reducer as Reducer< State, AnyAction >, actions, selectors, } ); + +declare module '@wordpress/data' { + // TODO: convert action.js to TS + function dispatch( + key: typeof STORE_NAME + ): DispatchFromMap< typeof actions >; + function select( + key: typeof STORE_NAME + ): SelectFromMap< typeof selectors >; +} diff --git a/packages/js/notices/src/store/reducer.js b/packages/js/notices/src/store/reducer.ts similarity index 54% rename from packages/js/notices/src/store/reducer.js rename to packages/js/notices/src/store/reducer.ts index ca68877d8d0..0fff8197b94 100644 --- a/packages/js/notices/src/store/reducer.js +++ b/packages/js/notices/src/store/reducer.ts @@ -2,22 +2,24 @@ * External dependencies */ import { reject } from 'lodash'; +import type { Reducer } from 'redux'; /** * Internal dependencies */ import onSubKey from './utils/on-sub-key'; +import { Action } from './actions'; +import { Notices } from './types'; /** - * Reducer returning the next notices state. The notices state is an object - * where each key is a context, its value an array of notice objects. + * Reducer returning the next notices state. The notices state is an array of notice objects * - * @param {Object} state Current state. + * @param {Array} state Current state. * @param {Object} action Dispatched action. * * @return {Object} Updated state. */ -const notices = onSubKey( 'context' )( ( state = [], action ) => { +const notices: Reducer< Notices, Action > = ( state = [], action ) => { switch ( action.type ) { case 'CREATE_NOTICE': // Avoid duplicates on ID. @@ -29,8 +31,12 @@ const notices = onSubKey( 'context' )( ( state = [], action ) => { case 'REMOVE_NOTICE': return reject( state, { id: action.id } ); } - return state; -} ); +}; -export default notices; +export type State = { + [ context: string ]: Notices; +}; + +// Creates a combined reducer object where each key is a context, its value an array of notice objects. +export default onSubKey( 'context' )( notices ); diff --git a/packages/js/notices/src/store/selectors.js b/packages/js/notices/src/store/selectors.ts similarity index 94% rename from packages/js/notices/src/store/selectors.js rename to packages/js/notices/src/store/selectors.ts index d03d8812d53..8daed4e3c9f 100644 --- a/packages/js/notices/src/store/selectors.js +++ b/packages/js/notices/src/store/selectors.ts @@ -2,6 +2,7 @@ * Internal dependencies */ import { DEFAULT_CONTEXT } from './constants'; +import { State } from './reducer'; /** @typedef {import('./actions').WPNoticeAction} WPNoticeAction */ @@ -14,7 +15,7 @@ import { DEFAULT_CONTEXT } from './constants'; * * @type {Array} */ -const DEFAULT_NOTICES = []; +const DEFAULT_NOTICES: [ ] = []; /** * @typedef {Object} WPNotice Notice object. @@ -51,6 +52,6 @@ const DEFAULT_NOTICES = []; * * @return {WPNotice[]} Array of notices. */ -export function getNotices( state, context = DEFAULT_CONTEXT ) { +export function getNotices( state: State, context: string = DEFAULT_CONTEXT ) { return state[ context ] || DEFAULT_NOTICES; } diff --git a/packages/js/notices/src/store/types.ts b/packages/js/notices/src/store/types.ts new file mode 100644 index 00000000000..69048b58061 --- /dev/null +++ b/packages/js/notices/src/store/types.ts @@ -0,0 +1,10 @@ +/** + * Internal dependencies + */ +import { createNotice } from './actions'; + +export type Notices = Array< ReturnType< typeof createNotice >[ 'notice' ] >; + +export type State = { + [ context: string ]: Notices; +}; diff --git a/packages/js/notices/src/store/utils/on-sub-key.js b/packages/js/notices/src/store/utils/on-sub-key.ts similarity index 70% rename from packages/js/notices/src/store/utils/on-sub-key.js rename to packages/js/notices/src/store/utils/on-sub-key.ts index 24adf06b773..cb29221c7b5 100644 --- a/packages/js/notices/src/store/utils/on-sub-key.js +++ b/packages/js/notices/src/store/utils/on-sub-key.ts @@ -1,3 +1,14 @@ +/** + * External dependencies + */ +import type { Reducer } from 'redux'; + +/** + * Internal dependencies + */ +import { State, Notices } from '../types'; +import { Action } from '../actions'; + /** * Higher-order reducer creator which creates a combined reducer object, keyed * by a property on the action object. @@ -6,10 +17,9 @@ * * @return {Function} Higher-order reducer. */ -export const onSubKey = ( actionProperty ) => ( reducer ) => ( - state = {}, - action -) => { +export const onSubKey = ( actionProperty: keyof Action ) => ( + reducer: Reducer< Notices, Action > +) => ( state: State = {}, action: Action ) => { // Retrieve subkey from action. Do not track if undefined; useful for cases // where reducer is scoped by action shape. const key = action[ actionProperty ]; diff --git a/packages/js/notices/tsconfig.json b/packages/js/notices/tsconfig.json index e8f14a25fa4..ea9f201d401 100644 --- a/packages/js/notices/tsconfig.json +++ b/packages/js/notices/tsconfig.json @@ -2,6 +2,9 @@ "extends": "../tsconfig", "compilerOptions": { "rootDir": "src", - "outDir": "build-module" + "outDir": "build-module", + "declaration": true, + "declarationMap": true, + "declarationDir": "./build-types" } -} \ No newline at end of file +} From 65a8dbcd5a772296558ae7f1d5a321b10981170a Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 20 May 2022 13:10:00 +0800 Subject: [PATCH 3/6] Add changelog --- .../js/notices/changelog/dev-33098-migrate-woo-notice-to-ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 packages/js/notices/changelog/dev-33098-migrate-woo-notice-to-ts diff --git a/packages/js/notices/changelog/dev-33098-migrate-woo-notice-to-ts b/packages/js/notices/changelog/dev-33098-migrate-woo-notice-to-ts new file mode 100644 index 00000000000..13cf6e2b7b2 --- /dev/null +++ b/packages/js/notices/changelog/dev-33098-migrate-woo-notice-to-ts @@ -0,0 +1,4 @@ +Significance: patch +Type: dev + +Migrate @woocommerce/notices to TS From 16bb4b633a24b5badf9c13fbe11258ea49695c34 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 20 May 2022 13:12:08 +0800 Subject: [PATCH 4/6] Update pnpm-lock.yaml --- packages/js/notices/package.json | 1 - pnpm-lock.yaml | 13 ++++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/packages/js/notices/package.json b/packages/js/notices/package.json index fb1fc95e9cd..fd88c0a4790 100644 --- a/packages/js/notices/package.json +++ b/packages/js/notices/package.json @@ -49,7 +49,6 @@ "@automattic/data-stores": "^2.0.1", "@babel/core": "^7.17.5", "@woocommerce/eslint-plugin": "workspace:*", - "@woocommerce/data": "workspace:*", "eslint": "^8.12.0", "jest": "^27.5.1", "jest-cli": "^27.5.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbd26cc6255..f9f2aab1052 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1005,6 +1005,7 @@ importers: packages/js/notices: specifiers: + '@automattic/data-stores': ^2.0.1 '@babel/core': ^7.17.5 '@woocommerce/eslint-plugin': workspace:* '@wordpress/a11y': ^3.5.0 @@ -1013,6 +1014,7 @@ importers: eslint: ^8.12.0 jest: ^27.5.1 jest-cli: ^27.5.1 + redux: ^4.2.0 rimraf: ^3.0.2 ts-jest: ^27.1.3 typescript: ^4.6.2 @@ -1021,11 +1023,13 @@ importers: '@wordpress/data': 6.4.1 '@wordpress/notices': 3.4.1 devDependencies: + '@automattic/data-stores': 2.0.1_@wordpress+data@6.4.1 '@babel/core': 7.17.8 '@woocommerce/eslint-plugin': link:../eslint-plugin eslint: 8.12.0 jest: 27.5.1 jest-cli: 27.5.1 + redux: 4.2.0 rimraf: 3.0.2 ts-jest: 27.1.3_b64eba0a9f1c7068f7f3a75addda5ccd typescript: 4.6.2 @@ -13711,6 +13715,7 @@ packages: re-resizable: 4.11.0 transitivePeerDependencies: - react + - react-dom dev: true /@types/wordpress__compose/4.0.1: @@ -18577,7 +18582,7 @@ packages: dev: true /buffer-crc32/0.2.13: - resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=} + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} /buffer-fill/1.0.0: resolution: {integrity: sha1-+PeLdniYiO858gXNY39o5wISKyw=} @@ -34644,6 +34649,12 @@ packages: dependencies: '@babel/runtime': 7.17.7 + /redux/4.2.0: + resolution: {integrity: sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==} + dependencies: + '@babel/runtime': 7.17.7 + dev: true + /reflect.ownkeys/0.2.0: resolution: {integrity: sha1-dJrO7H8/34tj+SegSAnpDFwLNGA=} From 5187c07e9e590b8b97ff45680561a727b764beb4 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Fri, 20 May 2022 14:43:38 +0800 Subject: [PATCH 5/6] Update createNotice type --- packages/js/notices/src/store/actions.ts | 35 +++++++++--------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/packages/js/notices/src/store/actions.ts b/packages/js/notices/src/store/actions.ts index 2f2b46bf9eb..8cadd7f9732 100644 --- a/packages/js/notices/src/store/actions.ts +++ b/packages/js/notices/src/store/actions.ts @@ -56,29 +56,20 @@ export type Options = { export function createNotice( status: Status = DEFAULT_STATUS, content: string, - { - speak, - isDismissible, - context, - id, - actions, - type, - __unstableHTML, - icon, - explicitDismiss, - onDismiss, - }: Options = { - speak: true, - isDismissible: true, - context: DEFAULT_CONTEXT, - id: uniqueId( DEFAULT_CONTEXT ), - actions: [], - type: 'default', - icon: null, - explicitDismiss: false, - onDismiss: null, - } + options: Partial< Options > = {} ) { + const { + speak = true, + isDismissible = true, + context = DEFAULT_CONTEXT, + id = uniqueId( DEFAULT_CONTEXT ), + actions = [], + type = 'default', + __unstableHTML = false, + icon = null, + explicitDismiss = false, + onDismiss = null, + } = options; // The supported value shape of content is currently limited to plain text // strings. To avoid setting expectation that e.g. a WPElement could be // supported, cast to a string. From da773d1730a23d829c6026a6422247babb6f3882 Mon Sep 17 00:00:00 2001 From: Chi-Hsuan Huang Date: Mon, 23 May 2022 12:03:12 +0800 Subject: [PATCH 6/6] Update default action value types --- packages/js/notices/src/store/actions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/js/notices/src/store/actions.ts b/packages/js/notices/src/store/actions.ts index 8cadd7f9732..0bc43dbe076 100644 --- a/packages/js/notices/src/store/actions.ts +++ b/packages/js/notices/src/store/actions.ts @@ -62,10 +62,10 @@ export function createNotice( speak = true, isDismissible = true, context = DEFAULT_CONTEXT, - id = uniqueId( DEFAULT_CONTEXT ), + id = uniqueId( context ), actions = [], type = 'default', - __unstableHTML = false, + __unstableHTML, icon = null, explicitDismiss = false, onDismiss = null,